From feb0206951cb1ffa37fb51d621a63a4f7fa35c8d Mon Sep 17 00:00:00 2001 From: alterNERDtive Date: Fri, 8 Jul 2022 20:22:50 +0200 Subject: [PATCH] more ground work --- ExamplePlugin/ExamplePlugin.cs | 31 ++- VoiceAttack-Framework/VoiceAttackLog.cs | 63 +++++- VoiceAttack-Framework/VoiceAttackPlugin.cs | 251 ++++++++++++++++----- 3 files changed, 266 insertions(+), 79 deletions(-) diff --git a/ExamplePlugin/ExamplePlugin.cs b/ExamplePlugin/ExamplePlugin.cs index 59650d9..4aa925c 100644 --- a/ExamplePlugin/ExamplePlugin.cs +++ b/ExamplePlugin/ExamplePlugin.cs @@ -64,8 +64,17 @@ namespace alterNERDtive.Yavapf.Example // they are to be executed for. // See the definition of the “Test” method below. Plugin.Contexts += Test; - Plugin.Contexts += EmptyContext; Plugin.Contexts += RegexContext; + + // You can even add lambdas! This one logs an explicit error for + // invoking the plugin without a context. + Plugin.Contexts += + [Context("")] + (vaProxy) + => + { + Plugin.Log.Error($"Invoking this plugin without a context is not allowed."); + }; } /// @@ -182,7 +191,9 @@ namespace alterNERDtive.Yavapf.Example { Plugin.Log.Notice( $"This is the example handler for the plugin contexts “test” and “different test”. It has been invoked with '{vaProxy.Context}'."); - _ = Plugin.Get("~test") ?? throw new ArgumentNullException("~test"); + + string test = Plugin.Get("~test") ?? throw new ArgumentNullException("~test"); + Plugin.Log.Notice($"The value of 'TXT:~test' is '{test}'"); } /// @@ -190,7 +201,7 @@ namespace alterNERDtive.Yavapf.Example /// specified with “Context” attributes. Contexts must be all lower case. /// /// This example demonstrates using regular expressions. It handles all - /// contexts that begin with “foo” or contain. + /// contexts that begin with “foo” or contain “bar”. /// /// The current VoiceAttack proxy object. [Context("^foo.*")] @@ -200,19 +211,5 @@ namespace alterNERDtive.Yavapf.Example Plugin.Log.Notice( $"This is the example handler for the plugin contexts “^foo.*” and “^.*bar.*”. It has been invoked with '{vaProxy.Context}'."); } - - /// - /// An example handler for plugin contexts. The context(s) must be - /// specified with “Context” attributes. Contexts must be all lower case. - /// - /// This example displays a custom error message when the plugin is - /// invoked with no context (= empty context). - /// - /// The current VoiceAttack proxy object. - [Context("")] - private static void EmptyContext(VoiceAttackInvokeProxyClass vaProxy) - { - Plugin.Log.Error($"Invoking this plugin without a context is not allowed."); - } } } diff --git a/VoiceAttack-Framework/VoiceAttackLog.cs b/VoiceAttack-Framework/VoiceAttackLog.cs index 33491c8..44f0e8c 100644 --- a/VoiceAttack-Framework/VoiceAttackLog.cs +++ b/VoiceAttack-Framework/VoiceAttackLog.cs @@ -23,6 +23,9 @@ using VoiceAttack; namespace alterNERDtive.Yavapf { + /// + /// Exposes methods for writing to the VoiceAttack event log. + /// public class VoiceAttackLog { private static readonly string[] LogColour = { "red", "yellow", "green", "blue", "gray" }; @@ -32,12 +35,20 @@ namespace alterNERDtive.Yavapf private readonly VoiceAttackInitProxyClass vaProxy; private readonly string id; - public VoiceAttackLog(VoiceAttackInitProxyClass vaProxy, string id) + /// + /// Initializes a new instance of the class. + /// + /// A VoiceAttackInitProxyClass object to interface with. + /// The plugin name. + public VoiceAttackLog(VoiceAttackInitProxyClass vaProxy, string name) { this.vaProxy = vaProxy; - this.id = id; + this.id = name; } + /// + /// Gets or sets the current log level. Default is NOTICE. + /// public LogLevel? LogLevel { get => logLevel ?? Yavapf.LogLevel.NOTICE; @@ -48,6 +59,12 @@ namespace alterNERDtive.Yavapf } } + /// + /// Sets the current log level. + /// + /// Valid values are ERROR, WARN, NOTICE, INFO and DEBUG. + /// + /// The new log level. public void SetLogLevel(string level) { if (level == null) @@ -60,6 +77,15 @@ namespace alterNERDtive.Yavapf } } + /// + /// Logs a given message with the specified log level. + /// + /// If the current log level is higher than the message’s log level it + /// will not be logged. + /// + /// The message to be logged. + /// The desired log level. + /// Thrown when the message is null. public void Log(string message, LogLevel level = Yavapf.LogLevel.INFO) { _ = message ?? throw new ArgumentNullException("message"); @@ -70,20 +96,51 @@ namespace alterNERDtive.Yavapf } } + /// + /// Logs a given message with the ERROR log level. + /// + /// The message to be logged. public void Error(string message) => this.Log(message, Yavapf.LogLevel.ERROR); + /// + /// Logs a given message with the WARN log level. + /// + /// If the current log level is ERROR the message will not be logged. + /// + /// The message to be logged. public void Warn(string message) => this.Log(message, Yavapf.LogLevel.WARN); + /// + /// Logs a given message with the NOTICE log level. + /// + /// If the current log level is ERROR or WARN the message will not be + /// logged. + /// + /// The message to be logged. public void Notice(string message) => this.Log(message, Yavapf.LogLevel.NOTICE); + /// + /// Logs a given message with the INFO log level. + /// + /// If the current log level is ERROR, WARN or NOTICE the message will + /// not be logged. + /// + /// The message to be logged. public void Info(string message) => this.Log(message, Yavapf.LogLevel.INFO); + /// + /// Logs a given message with the DEBUG log level. + /// + /// If the current log level is not DEBUG the message will not be logged. + /// + /// The message to be logged. public void Debug(string message) => this.Log(message, Yavapf.LogLevel.DEBUG); } /// - /// Log levels that can be used when writing to the VoiceAttack log. + /// Log levels that can be used when writing to the VoiceAttack event log. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Well it’s this or wrong file name, isn’t it?")] public enum LogLevel { /// diff --git a/VoiceAttack-Framework/VoiceAttackPlugin.cs b/VoiceAttack-Framework/VoiceAttackPlugin.cs index e2fe78f..6944476 100644 --- a/VoiceAttack-Framework/VoiceAttackPlugin.cs +++ b/VoiceAttack-Framework/VoiceAttackPlugin.cs @@ -21,114 +21,226 @@ using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Text.RegularExpressions; using VoiceAttack; namespace alterNERDtive.Yavapf { + /// + /// Framework class for implementing a VoiceAttack plugin. + /// public class VoiceAttackPlugin { + private VoiceAttackLog? log; + private VoiceAttackInitProxyClass? vaProxy; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the plugin. + /// The current version of the plugin. + /// The description of the plugin. + /// The GUID of the plugin. public VoiceAttackPlugin(string name, string version, string info, string guid) => (this.Name, this.Version, this.Info, this.Guid) = (name, version, info, new (guid)); - // this just hides the default constructor to make sure Properties are set. - private VoiceAttackPlugin() - { - } - + /// + /// Invoked when VoiceAttack initializes the plugin. + /// public event Action? Init; + /// + /// Invoked when VoiceAttack closes. + /// public event Action? Exit; + /// + /// Invoked when VoiceAttack stops all commands. + /// public event Action? Stop; + /// + /// Gets or sets the Actions to be run when the plugin is invoked from a + /// VoiceAttack command. Only Actions with a matching “Context” + /// attribute will be invoked. + /// public HandlerList> Contexts { get; set; } = new (); + /// + /// Gets the currently stored VoiceAttackInitProxyClass object which is + /// used to interface with VoiceAttack. + /// + /// You will usually want to use the provided methods and Properties + /// instead. + /// public VoiceAttackInitProxyClass Proxy { - get => this.vaProxy; + get => this.vaProxy!; } + /// + /// Gets the name of the plugin. + /// public string Name { get; } + /// + /// Gets the current version of the plugin. + /// public string Version { get; } + /// + /// Gets the description of the plugin. + /// public string Info { get; } + /// + /// Gets the GUID of the plugin. + /// public Guid Guid { get; } - public VoiceAttackLog Log { - get => this.log ??= new VoiceAttackLog(this.vaProxy, this.Name); + /// + /// Gets the instance the plugin uses to + /// log to the VoiceAttack event log. + /// + /// You can use this to log your own messages. + /// + public VoiceAttackLog Log + { + get => this.log ??= new VoiceAttackLog(this.Proxy, this.Name); } - private VoiceAttackLog log; - - protected VoiceAttackInitProxyClass vaProxy; + /// + /// Gets the value of a variable from VoiceAttack. + /// + /// Valid varible types are , , + /// , , and + /// . + /// + /// The type of the variable. + /// The name of the variable. + /// The value of the variable. Can be null. + /// Thrown when the variable is of an invalid type. + public T? Get(string name) + { + dynamic? ret = typeof(T) switch + { + Type boolType when boolType == typeof(bool) => this.Proxy.GetBoolean(name), + Type dateType when dateType == typeof(DateTime) => this.Proxy.GetDate(name), + Type decType when decType == typeof(decimal) => this.Proxy.GetDecimal(name), + Type intType when intType == typeof(int) => this.Proxy.GetInt(name), + Type shortType when shortType == typeof(short) => this.Proxy.GetSmallInt(name), + Type stringType when stringType == typeof(string) => this.Proxy.GetText(name), + _ => throw new InvalidDataException($"Cannot get variable '{name}': invalid type '{typeof(T).Name}'."), + }; + return ret; + } + /// + /// Sets a variable for use in VoiceAttack. + /// + /// Valid varible types are , , + /// , , and + /// . + /// + /// The type of the variable. + /// The name of the variable. + /// The value of the variable. Can not be null. + /// Thrown when the variable is of an invalid type. public void Set(string name, T? value) { - switch (value) + if (value == null) { - case bool b: - this.vaProxy.SetBoolean(name, b); + this.Unset(name); + } + else + { + switch (value) + { + case bool b: + this.Proxy.SetBoolean(name, b); + break; + case DateTime d: + this.Proxy.SetDate(name, d); + break; + case decimal d: + this.Proxy.SetDecimal(name, d); + break; + case int i: + this.Proxy.SetInt(name, i); + break; + case short s: + this.Proxy.SetSmallInt(name, s); + break; + case string s: + this.Proxy.SetText(name, s); + break; + default: + throw new InvalidDataException($"Cannot set variable '{name}': invalid type '{typeof(T).Name}'."); + } + } + } + + /// + /// Unsets a variable for use in VoiceAttack (= sets it to null). + /// + /// Valid varible types are , , + /// , , and + /// . + /// + /// The type of the variable. + /// The name of the variable. + /// Thrown when the variable is of an invalid type. + public void Unset(string name) + { + switch (typeof(T)) + { + case Type boolType when boolType == typeof(bool): + this.Proxy.SetBoolean(name, null); break; - case DateTime d: - this.vaProxy.SetDate(name, d); + case Type dateType when dateType == typeof(DateTime): + this.Proxy.SetDate(name, null); break; - case decimal d: - this.vaProxy.SetDecimal(name, d); + case Type decType when decType == typeof(decimal): + this.Proxy.SetDecimal(name, null); break; - case int i: - this.vaProxy.SetInt(name, i); + case Type intType when intType == typeof(int): + this.Proxy.SetInt(name, null); break; - case short s: - this.vaProxy.SetSmallInt(name, s); + case Type shortType when shortType == typeof(short): + this.Proxy.SetSmallInt(name, null); break; - case string s: - this.vaProxy.SetText(name, s); + case Type stringType when stringType == typeof(string): + this.Proxy.SetText(name, null); break; default: throw new InvalidDataException($"Cannot set variable '{name}': invalid type '{typeof(T).Name}'."); } } - public T? Get(string name) - { - dynamic? ret = null; - - switch (default(T)) - { - case bool _: - ret = this.vaProxy.GetBoolean(name); - break; - case DateTime _: - ret = this.vaProxy.GetDate(name); - break; - case decimal _: - ret = this.vaProxy.GetDecimal(name); - break; - case int _: - ret = this.vaProxy.GetInt(name); - break; - case short _: - ret = this.vaProxy.GetSmallInt(name); - break; - case string _: - ret = this.vaProxy.GetText(name); - break; - default: - throw new InvalidDataException($"Cannot get variable '{name}': invalid type '{typeof(T).Name}'."); - } - - return ret; - } - + /// + /// The plugin’s display name, as required by the VoiceAttack plugin API. + /// + /// The display name. public string VA_DisplayName() => $"{this.Name} v{this.Version}"; + /// + /// The plugin’s description, as required by the VoiceAttack plugin API. + /// + /// The description. public string VA_DisplayInfo() => this.Info; + /// + /// The plugin’s GUID, as required by the VoiceAttack plugin API. + /// + /// The GUID. public Guid VA_Id() => this.Guid; + /// + /// The Init method, as required by the VoiceAttack plugin API. + /// Runs when the plugin is initially loaded. + /// + /// The VoiceAttack proxy object. public void VA_Init1(VoiceAttackInitProxyClass vaProxy) { this.vaProxy = vaProxy; @@ -150,6 +262,11 @@ namespace alterNERDtive.Yavapf this.Log.Debug("Initialized."); } + /// + /// The Invoke method, as required by the VoiceAttack plugin API. + /// Runs whenever a plugin context is invoked. + /// + /// The VoiceAttack proxy object. public void VA_Invoke1(VoiceAttackInvokeProxyClass vaProxy) { this.vaProxy = vaProxy; @@ -159,14 +276,20 @@ namespace alterNERDtive.Yavapf try { bool exists = false; - foreach (Action action in Contexts) + foreach (Action action in this.Contexts) { foreach (ContextAttribute attr in action.Method.GetCustomAttributes()) { - if (attr.Name == context) + if (attr.Name == context || + (attr.Name.StartsWith("^") && Regex.Match(context, attr.Name).Success)) { exists = true; action.Invoke(vaProxy); + + // Make sure that we don’t run the same Action + // multiple times just because it has e.g. multiple + // matching context regexes. + break; } } } @@ -186,21 +309,31 @@ namespace alterNERDtive.Yavapf } } + /// + /// The Exit method, as required by the VoiceAttack plugin API. + /// Runs when VoiceAttack is shut down. + /// + /// The VoiceAttack proxy object. public void VA_Exit1(VoiceAttackProxyClass vaProxy) { this.Exit?.Invoke(vaProxy); } + /// + /// The StopCommand method, as required by the VoiceAttack plugin API. + /// Runs whenever all commands are stopped using the “Stop All Commands” + /// button or action. + /// public void VA_StopCommand() { this.Stop?.Invoke(); } - private void TextVariableChanged(string name, string from, string to, Guid? internalID = null) + protected void TextVariableChanged(string name, string from, string to, Guid? internalID = null) { } - private void IntegerVariableChanged(string name, int? from, int? to, Guid? internalID = null) + protected void IntegerVariableChanged(string name, int? from, int? to, Guid? internalID = null) { }