summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-02-13 00:40:33 -0500
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-02-13 00:40:33 -0500
commit0441d0843c65775bc72377e32ed4b3b5ee0b8f75 (patch)
treeca2f098e04c968ef3bee32ce7900c68b868d17d2
parentd1080a8b2b54c777a446f08d9ecd5b76b4b2561a (diff)
downloadSMAPI-0441d0843c65775bc72377e32ed4b3b5ee0b8f75.tar.gz
SMAPI-0441d0843c65775bc72377e32ed4b3b5ee0b8f75.tar.bz2
SMAPI-0441d0843c65775bc72377e32ed4b3b5ee0b8f75.zip
add new console command API with backward compatibility (#199)
-rw-r--r--src/StardewModdingAPI/Command.cs77
-rw-r--r--src/StardewModdingAPI/Events/EventArgsCommand.cs1
-rw-r--r--src/StardewModdingAPI/Framework/Command.cs40
-rw-r--r--src/StardewModdingAPI/Framework/CommandHelper.cs53
-rw-r--r--src/StardewModdingAPI/Framework/CommandManager.cs114
-rw-r--r--src/StardewModdingAPI/Framework/ModHelper.cs8
-rw-r--r--src/StardewModdingAPI/ICommandHelper.cs26
-rw-r--r--src/StardewModdingAPI/IModHelper.cs3
-rw-r--r--src/StardewModdingAPI/Program.cs32
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.csproj4
10 files changed, 317 insertions, 41 deletions
diff --git a/src/StardewModdingAPI/Command.cs b/src/StardewModdingAPI/Command.cs
index 6195bd8b..3b1e5632 100644
--- a/src/StardewModdingAPI/Command.cs
+++ b/src/StardewModdingAPI/Command.cs
@@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework;
namespace StardewModdingAPI
{
/// <summary>A command that can be submitted through the SMAPI console to interact with SMAPI.</summary>
+ [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ConsoleCommands))]
public class Command
{
/*********
@@ -16,7 +16,7 @@ namespace StardewModdingAPI
** SMAPI
****/
/// <summary>The commands registered with SMAPI.</summary>
- internal static List<Command> RegisteredCommands = new List<Command>();
+ private static readonly IDictionary<string, Command> LegacyCommands = new Dictionary<string, Command>(StringComparer.InvariantCultureIgnoreCase);
/// <summary>The event raised when this command is submitted through the console.</summary>
public event EventHandler<EventArgsCommand> CommandFired;
@@ -64,6 +64,7 @@ namespace StardewModdingAPI
this.CommandFired.Invoke(this, new EventArgsCommand(this));
}
+
/****
** SMAPI
****/
@@ -72,27 +73,7 @@ namespace StardewModdingAPI
/// <param name="monitor">Encapsulates monitoring and logging.</param>
public static void CallCommand(string input, IMonitor monitor)
{
- // normalise input
- input = input?.Trim();
- if (string.IsNullOrWhiteSpace(input))
- return;
-
- // tokenise input
- string[] args = input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
- string commandName = args[0];
- args = args.Skip(1).ToArray();
-
- // get command
- Command command = Command.FindCommand(commandName);
- if (command == null)
- {
- monitor.Log("Unknown command", LogLevel.Error);
- return;
- }
-
- // fire command
- command.CalledArgs = args;
- command.Fire();
+ Program.CommandManager.Trigger(input);
}
/// <summary>Register a command with SMAPI.</summary>
@@ -101,11 +82,25 @@ namespace StardewModdingAPI
/// <param name="args">A human-readable list of accepted arguments.</param>
public static Command RegisterCommand(string name, string description, string[] args = null)
{
- var command = new Command(name, description, args);
- if (Command.RegisteredCommands.Contains(command))
- throw new InvalidOperationException($"The '{command.CommandName}' command is already registered!");
+ name = name?.Trim().ToLower();
+
+ // raise deprecation warning
+ Program.DeprecationManager.Warn("Command.RegisterCommand", "1.9", DeprecationLevel.Notice);
- Command.RegisteredCommands.Add(command);
+ // validate
+ if (Command.LegacyCommands.ContainsKey(name))
+ throw new InvalidOperationException($"The '{name}' command is already registered!");
+
+ // add command
+ string modName = Program.ModRegistry.GetModFromStack() ?? "<unknown mod>";
+ string documentation = args?.Length > 0
+ ? $"{description} - {string.Join(", ", args)}"
+ : description;
+ Program.CommandManager.Add(modName, name, documentation, Command.Fire);
+
+ // add legacy command
+ Command command = new Command(name, description, args);
+ Command.LegacyCommands.Add(name, command);
return command;
}
@@ -113,7 +108,33 @@ namespace StardewModdingAPI
/// <param name="name">The command name to find.</param>
public static Command FindCommand(string name)
{
- return Command.RegisteredCommands.Find(x => x.CommandName.Equals(name));
+ if (name == null)
+ return null;
+
+ Command command;
+ Command.LegacyCommands.TryGetValue(name.Trim(), out command);
+ return command;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Trigger this command.</summary>
+ /// <param name="name">The command name.</param>
+ /// <param name="args">The command arguments.</param>
+ private static void Fire(string name, string[] args)
+ {
+ // get legacy command
+ Command command;
+ if (!Command.LegacyCommands.TryGetValue(name, out command))
+ {
+ throw new InvalidOperationException($"Can't run command '{name}' because there's no such legacy command.");
+ return;
+ }
+
+ // raise event
+ command.Fire();
}
}
}
diff --git a/src/StardewModdingAPI/Events/EventArgsCommand.cs b/src/StardewModdingAPI/Events/EventArgsCommand.cs
index ddf644fb..bae13694 100644
--- a/src/StardewModdingAPI/Events/EventArgsCommand.cs
+++ b/src/StardewModdingAPI/Events/EventArgsCommand.cs
@@ -3,6 +3,7 @@
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for a <see cref="StardewModdingAPI.Command.CommandFired"/> event.</summary>
+ [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ConsoleCommands))]
public class EventArgsCommand : EventArgs
{
/*********
diff --git a/src/StardewModdingAPI/Framework/Command.cs b/src/StardewModdingAPI/Framework/Command.cs
new file mode 100644
index 00000000..943e018d
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/Command.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>A command that can be submitted through the SMAPI console to interact with SMAPI.</summary>
+ internal class Command
+ {
+ /*********
+ ** Accessor
+ *********/
+ /// <summary>The friendly name for the mod that registered the command.</summary>
+ public string ModName { get; }
+
+ /// <summary>The command name, which the user must type to trigger it.</summary>
+ public string Name { get; }
+
+ /// <summary>The human-readable documentation shown when the player runs the built-in 'help' command.</summary>
+ public string Documentation { get; }
+
+ /// <summary>The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</summary>
+ public Action<string, string[]> Callback { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="modName">The friendly name for the mod that registered the command.</param>
+ /// <param name="name">The command name, which the user must type to trigger it.</param>
+ /// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param>
+ /// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param>
+ public Command(string modName, string name, string documentation, Action<string, string[]> callback)
+ {
+ this.ModName = modName;
+ this.Name = name;
+ this.Documentation = documentation;
+ this.Callback = callback;
+ }
+ }
+}
diff --git a/src/StardewModdingAPI/Framework/CommandHelper.cs b/src/StardewModdingAPI/Framework/CommandHelper.cs
new file mode 100644
index 00000000..2e9dea8e
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/CommandHelper.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>Provides an API for managing console commands.</summary>
+ internal class CommandHelper : ICommandHelper
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The friendly mod name for this instance.</summary>
+ private readonly string ModName;
+
+ /// <summary>Manages console commands.</summary>
+ private readonly CommandManager CommandManager;
+
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="modName">The friendly mod name for this instance.</param>
+ /// <param name="commandManager">Manages console commands.</param>
+ public CommandHelper(string modName, CommandManager commandManager)
+ {
+ this.ModName = modName;
+ this.CommandManager = commandManager;
+ }
+
+ /// <summary>Add a console command.</summary>
+ /// <param name="name">The command name, which the user must type to trigger it.</param>
+ /// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param>
+ /// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="callback"/> is null or empty.</exception>
+ /// <exception cref="FormatException">The <paramref name="name"/> is not a valid format.</exception>
+ /// <exception cref="ArgumentException">There's already a command with that name.</exception>
+ public ICommandHelper Add(string name, string documentation, Action<string, string[]> callback)
+ {
+ this.CommandManager.Add(this.ModName, name, documentation, callback);
+ return this;
+ }
+
+ /// <summary>Trigger a command.</summary>
+ /// <param name="name">The command name.</param>
+ /// <param name="arguments">The command arguments.</param>
+ /// <returns>Returns whether a matching command was triggered.</returns>
+ public bool Trigger(string name, string[] arguments)
+ {
+ return this.CommandManager.Trigger(name, arguments);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/CommandManager.cs b/src/StardewModdingAPI/Framework/CommandManager.cs
new file mode 100644
index 00000000..3aa4bf97
--- /dev/null
+++ b/src/StardewModdingAPI/Framework/CommandManager.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>Manages console commands.</summary>
+ internal class CommandManager
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>The commands registered with SMAPI.</summary>
+ private readonly IDictionary<string, Command> Commands = new Dictionary<string, Command>(StringComparer.InvariantCultureIgnoreCase);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Add a console command.</summary>
+ /// <param name="modName">The friendly mod name for this instance.</param>
+ /// <param name="name">The command name, which the user must type to trigger it.</param>
+ /// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param>
+ /// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param>
+ /// <param name="allowNullCallback">Whether to allow a null <paramref name="callback"/> argument; this should only used for backwards compatibility.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="callback"/> is null or empty.</exception>
+ /// <exception cref="FormatException">The <paramref name="name"/> is not a valid format.</exception>
+ /// <exception cref="ArgumentException">There's already a command with that name.</exception>
+ public void Add(string modName, string name, string documentation, Action<string, string[]> 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(modName, name, documentation, callback));
+ }
+
+ /// <summary>Get a command by its unique name.</summary>
+ /// <param name="name">The command name.</param>
+ /// <returns>Returns the matching command, or <c>null</c> if not found.</returns>
+ public Command Get(string name)
+ {
+ name = this.GetNormalisedName(name);
+ Command command;
+ this.Commands.TryGetValue(name, out command);
+ return command;
+ }
+
+ /// <summary>Get all registered commands.</summary>
+ public IEnumerable<Command> GetAll()
+ {
+ return this.Commands
+ .Values
+ .OrderBy(p => p.Name);
+ }
+
+ /// <summary>Trigger a command.</summary>
+ /// <param name="input">The raw command input.</param>
+ /// <returns>Returns whether a matching command was triggered.</returns>
+ public bool Trigger(string input)
+ {
+ string[] args = input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ string name = args[0];
+ args = args.Skip(1).ToArray();
+
+ return this.Trigger(name, args);
+ }
+
+ /// <summary>Trigger a command.</summary>
+ /// <param name="name">The command name.</param>
+ /// <param name="arguments">The command arguments.</param>
+ /// <returns>Returns whether a matching command was triggered.</returns>
+ public bool Trigger(string name, string[] arguments)
+ {
+ // get normalised name
+ name = this.GetNormalisedName(name);
+ if (name == null)
+ return false;
+
+ // get command
+ Command command;
+ if (this.Commands.TryGetValue(name, out command))
+ {
+ command.Callback.Invoke(name, arguments);
+ return true;
+ }
+ return false;
+ }
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get a normalised command name.</summary>
+ /// <param name="name">The command name.</param>
+ private string GetNormalisedName(string name)
+ {
+ name = name?.Trim().ToLower();
+ return !string.IsNullOrWhiteSpace(name)
+ ? name
+ : null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs
index 4a3d5ed5..04767de5 100644
--- a/src/StardewModdingAPI/Framework/ModHelper.cs
+++ b/src/StardewModdingAPI/Framework/ModHelper.cs
@@ -27,17 +27,22 @@ namespace StardewModdingAPI.Framework
/// <summary>Metadata about loaded mods.</summary>
public IModRegistry ModRegistry { get; }
+ /// <summary>An API for managing console commands.</summary>
+ public ICommandHelper ConsoleCommands { get; }
+
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
+ /// <param name="modName">The friendly mod name.</param>
/// <param name="modDirectory">The mod directory path.</param>
/// <param name="jsonHelper">Encapsulate SMAPI's JSON parsing.</param>
/// <param name="modRegistry">Metadata about loaded mods.</param>
+ /// <param name="commandManager">Manages console commands.</param>
/// <exception cref="ArgumentNullException">An argument is null or empty.</exception>
/// <exception cref="InvalidOperationException">The <paramref name="modDirectory"/> path does not exist on disk.</exception>
- public ModHelper(string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry)
+ public ModHelper(string modName, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager)
{
// validate
if (string.IsNullOrWhiteSpace(modDirectory))
@@ -53,6 +58,7 @@ namespace StardewModdingAPI.Framework
this.JsonHelper = jsonHelper;
this.DirectoryPath = modDirectory;
this.ModRegistry = modRegistry;
+ this.ConsoleCommands = new CommandHelper(modName, commandManager);
}
/****
diff --git a/src/StardewModdingAPI/ICommandHelper.cs b/src/StardewModdingAPI/ICommandHelper.cs
new file mode 100644
index 00000000..3a51ffb4
--- /dev/null
+++ b/src/StardewModdingAPI/ICommandHelper.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace StardewModdingAPI
+{
+ /// <summary>Provides an API for managing console commands.</summary>
+ public interface ICommandHelper
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Add a console command.</summary>
+ /// <param name="name">The command name, which the user must type to trigger it.</param>
+ /// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param>
+ /// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="callback"/> is null or empty.</exception>
+ /// <exception cref="FormatException">The <paramref name="name"/> is not a valid format.</exception>
+ /// <exception cref="ArgumentException">There's already a command with that name.</exception>
+ ICommandHelper Add(string name, string documentation, Action<string, string[]> callback);
+
+ /// <summary>Trigger a command.</summary>
+ /// <param name="name">The command name.</param>
+ /// <param name="arguments">The command arguments.</param>
+ /// <returns>Returns whether a matching command was triggered.</returns>
+ bool Trigger(string name, string[] arguments);
+ }
+}
diff --git a/src/StardewModdingAPI/IModHelper.cs b/src/StardewModdingAPI/IModHelper.cs
index 02f9c038..ef67cd1c 100644
--- a/src/StardewModdingAPI/IModHelper.cs
+++ b/src/StardewModdingAPI/IModHelper.cs
@@ -15,6 +15,9 @@
/// <summary>Metadata about loaded mods.</summary>
IModRegistry ModRegistry { get; }
+ /// <summary>An API for managing console commands.</summary>
+ ICommandHelper ConsoleCommands { get; }
+
/*********
** Public methods
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs
index b86e186f..ff0dff29 100644
--- a/src/StardewModdingAPI/Program.cs
+++ b/src/StardewModdingAPI/Program.cs
@@ -84,6 +84,9 @@ namespace StardewModdingAPI
/// <summary>Manages deprecation warnings.</summary>
internal static readonly DeprecationManager DeprecationManager = new DeprecationManager(Program.Monitor, Program.ModRegistry);
+ /// <summary>Manages console commands.</summary>
+ internal static readonly CommandManager CommandManager = new CommandManager();
+
/*********
** Public methods
@@ -262,7 +265,7 @@ namespace StardewModdingAPI
while (!Program.ready) Thread.Sleep(1000);
// register help command
- Command.RegisterCommand("help", "Lists all commands | 'help <cmd>' returns command description").CommandFired += Program.help_CommandFired;
+ Program.CommandManager.Add("SMAPI", "help", "Lists all commands | 'help <cmd>' returns command description", Program.HandleHelpCommand);
// listen for command line input
Program.Monitor.Log("Starting console...");
@@ -506,7 +509,7 @@ namespace StardewModdingAPI
// inject data
// get helper
mod.ModManifest = manifest;
- mod.Helper = new ModHelper(directory.FullName, jsonHelper, Program.ModRegistry);
+ mod.Helper = new ModHelper(manifest.Name, directory.FullName, jsonHelper, Program.ModRegistry, Program.CommandManager);
mod.Monitor = Program.GetSecondaryMonitor(manifest.Name);
mod.PathOnDisk = directory.FullName;
@@ -556,24 +559,29 @@ namespace StardewModdingAPI
private static void ConsoleInputLoop()
{
while (true)
- Command.CallCommand(Console.ReadLine(), Program.Monitor);
+ {
+ string input = Console.ReadLine();
+ if (!Program.CommandManager.Trigger(input))
+ Program.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
+ }
}
/// <summary>The method called when the user submits the help command in the console.</summary>
- /// <param name="sender">The event sender.</param>
- /// <param name="e">The event data.</param>
- private static void help_CommandFired(object sender, EventArgsCommand e)
+ /// <param name="name">The command name.</param>
+ /// <param name="arguments">The command arguments.</param>
+ private static void HandleHelpCommand(string name, string[] arguments)
{
- if (e.Command.CalledArgs.Length > 0)
+ if (arguments.Any())
{
- var command = Command.FindCommand(e.Command.CalledArgs[0]);
- if (command == null)
- Program.Monitor.Log("The specified command could't be found", LogLevel.Error);
+
+ Framework.Command result = Program.CommandManager.Get(arguments[0]);
+ if (result == null)
+ Program.Monitor.Log("There's no command with that name.", LogLevel.Error);
else
- Program.Monitor.Log(command.CommandArgs.Length > 0 ? $"{command.CommandName}: {command.CommandDesc} - {string.Join(", ", command.CommandArgs)}" : $"{command.CommandName}: {command.CommandDesc}", LogLevel.Info);
+ Program.Monitor.Log($"{result.Name}: {result.Documentation} [added by {result.ModName}]", LogLevel.Info);
}
else
- Program.Monitor.Log("Commands: " + string.Join(", ", Command.RegisteredCommands.Select(x => x.CommandName)), LogLevel.Info);
+ Program.Monitor.Log("Commands: " + string.Join(", ", Program.CommandManager.GetAll().Select(p => p.Name)), LogLevel.Info);
}
/// <summary>Show a 'press any key to exit' message, and exit when they press a key.</summary>
diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj
index add6ec40..ffeb8e2e 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.csproj
+++ b/src/StardewModdingAPI/StardewModdingAPI.csproj
@@ -117,6 +117,7 @@
<Compile Include="Advanced\ConfigFile.cs" />
<Compile Include="Advanced\IConfigFile.cs" />
<Compile Include="Command.cs" />
+ <Compile Include="Framework\Command.cs" />
<Compile Include="Config.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Events\ControlEvents.cs" />
@@ -145,11 +146,14 @@
<Compile Include="Events\GraphicsEvents.cs" />
<Compile Include="Framework\AssemblyDefinitionResolver.cs" />
<Compile Include="Framework\AssemblyParseResult.cs" />
+ <Compile Include="Framework\CommandManager.cs" />
<Compile Include="Framework\Logging\ConsoleInterceptionManager.cs" />
<Compile Include="Framework\Logging\InterceptingTextWriter.cs" />
+ <Compile Include="Framework\CommandHelper.cs" />
<Compile Include="Framework\Reflection\PrivateProperty.cs" />
<Compile Include="Framework\Serialisation\JsonHelper.cs" />
<Compile Include="Framework\Serialisation\SemanticVersionConverter.cs" />
+ <Compile Include="ICommandHelper.cs" />
<Compile Include="IModRegistry.cs" />
<Compile Include="Events\LocationEvents.cs" />
<Compile Include="Events\MenuEvents.cs" />