more ground work

This commit is contained in:
alterNERDtive 2022-07-08 20:22:50 +02:00
parent 64d4b26d42
commit feb0206951
3 changed files with 266 additions and 79 deletions

View file

@ -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.");
};
}
/// <summary>
@ -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<string?>("~test") ?? throw new ArgumentNullException("~test");
string test = Plugin.Get<string?>("~test") ?? throw new ArgumentNullException("~test");
Plugin.Log.Notice($"The value of 'TXT:~test' is '{test}'");
}
/// <summary>
@ -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”.
/// </summary>
/// <param name="vaProxy">The current VoiceAttack proxy object.</param>
[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}'.");
}
/// <summary>
/// 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).
/// </summary>
/// <param name="vaProxy">The current VoiceAttack proxy object.</param>
[Context("")]
private static void EmptyContext(VoiceAttackInvokeProxyClass vaProxy)
{
Plugin.Log.Error($"Invoking this plugin without a context is not allowed.");
}
}
}

View file

@ -23,6 +23,9 @@ using VoiceAttack;
namespace alterNERDtive.Yavapf
{
/// <summary>
/// Exposes methods for writing to the VoiceAttack event log.
/// </summary>
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)
/// <summary>
/// Initializes a new instance of the <see cref="VoiceAttackLog"/> class.
/// </summary>
/// <param name="vaProxy">A VoiceAttackInitProxyClass object to interface with.</param>
/// <param name="name">The plugin name.</param>
public VoiceAttackLog(VoiceAttackInitProxyClass vaProxy, string name)
{
this.vaProxy = vaProxy;
this.id = id;
this.id = name;
}
/// <summary>
/// Gets or sets the current log level. Default is NOTICE.
/// </summary>
public LogLevel? LogLevel
{
get => logLevel ?? Yavapf.LogLevel.NOTICE;
@ -48,6 +59,12 @@ namespace alterNERDtive.Yavapf
}
}
/// <summary>
/// Sets the current log level.
///
/// Valid values are ERROR, WARN, NOTICE, INFO and DEBUG.
/// </summary>
/// <param name="level">The new log level.</param>
public void SetLogLevel(string level)
{
if (level == null)
@ -60,6 +77,15 @@ namespace alterNERDtive.Yavapf
}
}
/// <summary>
/// Logs a given message with the specified log level.
///
/// If the current log level is higher than the messages log level it
/// will not be logged.
/// </summary>
/// <param name="message">The message to be logged.</param>
/// <param name="level">The desired log level.</param>
/// <exception cref="ArgumentNullException">Thrown when the message is null.</exception>
public void Log(string message, LogLevel level = Yavapf.LogLevel.INFO)
{
_ = message ?? throw new ArgumentNullException("message");
@ -70,20 +96,51 @@ namespace alterNERDtive.Yavapf
}
}
/// <summary>
/// Logs a given message with the ERROR log level.
/// </summary>
/// <param name="message">The message to be logged.</param>
public void Error(string message) => this.Log(message, Yavapf.LogLevel.ERROR);
/// <summary>
/// Logs a given message with the WARN log level.
///
/// If the current log level is ERROR the message will not be logged.
/// </summary>
/// <param name="message">The message to be logged.</param>
public void Warn(string message) => this.Log(message, Yavapf.LogLevel.WARN);
/// <summary>
/// Logs a given message with the NOTICE log level.
///
/// If the current log level is ERROR or WARN the message will not be
/// logged.
/// </summary>
/// <param name="message">The message to be logged.</param>
public void Notice(string message) => this.Log(message, Yavapf.LogLevel.NOTICE);
/// <summary>
/// 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.
/// </summary>
/// <param name="message">The message to be logged.</param>
public void Info(string message) => this.Log(message, Yavapf.LogLevel.INFO);
/// <summary>
/// Logs a given message with the DEBUG log level.
///
/// If the current log level is not DEBUG the message will not be logged.
/// </summary>
/// <param name="message">The message to be logged.</param>
public void Debug(string message) => this.Log(message, Yavapf.LogLevel.DEBUG);
}
/// <summary>
/// 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.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Well its this or wrong file name, isnt it?")]
public enum LogLevel
{
/// <summary>

View file

@ -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
{
/// <summary>
/// Framework class for implementing a VoiceAttack plugin.
/// </summary>
public class VoiceAttackPlugin
{
private VoiceAttackLog? log;
private VoiceAttackInitProxyClass? vaProxy;
/// <summary>
/// Initializes a new instance of the <see cref="VoiceAttackPlugin"/> class.
/// </summary>
/// <param name="name">The name of the plugin.</param>
/// <param name="version">The current version of the plugin.</param>
/// <param name="info">The description of the plugin.</param>
/// <param name="guid">The GUID of the plugin.</param>
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()
{
}
/// <summary>
/// Invoked when VoiceAttack initializes the plugin.
/// </summary>
public event Action<VoiceAttackInitProxyClass>? Init;
/// <summary>
/// Invoked when VoiceAttack closes.
/// </summary>
public event Action<VoiceAttackProxyClass>? Exit;
/// <summary>
/// Invoked when VoiceAttack stops all commands.
/// </summary>
public event Action? Stop;
/// <summary>
/// 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.
/// </summary>
public HandlerList<Action<VoiceAttackInvokeProxyClass>> Contexts { get; set; } = new ();
/// <summary>
/// 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.
/// </summary>
public VoiceAttackInitProxyClass Proxy
{
get => this.vaProxy;
get => this.vaProxy!;
}
/// <summary>
/// Gets the name of the plugin.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the current version of the plugin.
/// </summary>
public string Version { get; }
/// <summary>
/// Gets the description of the plugin.
/// </summary>
public string Info { get; }
/// <summary>
/// Gets the GUID of the plugin.
/// </summary>
public Guid Guid { get; }
public VoiceAttackLog Log {
get => this.log ??= new VoiceAttackLog(this.vaProxy, this.Name);
/// <summary>
/// Gets the <see cref="VoiceAttackLog"/> instance the plugin uses to
/// log to the VoiceAttack event log.
///
/// You can use this to log your own messages.
/// </summary>
public VoiceAttackLog Log
{
get => this.log ??= new VoiceAttackLog(this.Proxy, this.Name);
}
private VoiceAttackLog log;
protected VoiceAttackInitProxyClass vaProxy;
/// <summary>
/// Gets the value of a variable from VoiceAttack.
///
/// Valid varible types are <see cref="bool"/>, <see cref="DateTime"/>,
/// <see cref="decimal"/>, <see cref="int"/>, <see cref="short"/> and
/// <see cref="string"/>.
/// </summary>
/// <typeparam name="T">The type of the variable.</typeparam>
/// <param name="name">The name of the variable.</param>
/// <returns>The value of the variable. Can be null.</returns>
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
public T? Get<T>(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;
}
/// <summary>
/// Sets a variable for use in VoiceAttack.
///
/// Valid varible types are <see cref="bool"/>, <see cref="DateTime"/>,
/// <see cref="decimal"/>, <see cref="int"/>, <see cref="short"/> and
/// <see cref="string"/>.
/// </summary>
/// <typeparam name="T">The type of the variable.</typeparam>
/// <param name="name">The name of the variable.</param>
/// <param name="value">The value of the variable. Can not be null.</param>
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
public void Set<T>(string name, T? value)
{
switch (value)
if (value == null)
{
case bool b:
this.vaProxy.SetBoolean(name, b);
this.Unset<T>(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}'.");
}
}
}
/// <summary>
/// Unsets a variable for use in VoiceAttack (= sets it to null).
///
/// Valid varible types are <see cref="bool"/>, <see cref="DateTime"/>,
/// <see cref="decimal"/>, <see cref="int"/>, <see cref="short"/> and
/// <see cref="string"/>.
/// </summary>
/// <typeparam name="T">The type of the variable.</typeparam>
/// <param name="name">The name of the variable.</param>
/// <exception cref="InvalidDataException">Thrown when the variable is of an invalid type.</exception>
public void Unset<T>(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<T>(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;
}
/// <summary>
/// The plugins display name, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The display name.</returns>
public string VA_DisplayName() => $"{this.Name} v{this.Version}";
/// <summary>
/// The plugins description, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The description.</returns>
public string VA_DisplayInfo() => this.Info;
/// <summary>
/// The plugins GUID, as required by the VoiceAttack plugin API.
/// </summary>
/// <returns>The GUID.</returns>
public Guid VA_Id() => this.Guid;
/// <summary>
/// The Init method, as required by the VoiceAttack plugin API.
/// Runs when the plugin is initially loaded.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public void VA_Init1(VoiceAttackInitProxyClass vaProxy)
{
this.vaProxy = vaProxy;
@ -150,6 +262,11 @@ namespace alterNERDtive.Yavapf
this.Log.Debug("Initialized.");
}
/// <summary>
/// The Invoke method, as required by the VoiceAttack plugin API.
/// Runs whenever a plugin context is invoked.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public void VA_Invoke1(VoiceAttackInvokeProxyClass vaProxy)
{
this.vaProxy = vaProxy;
@ -159,14 +276,20 @@ namespace alterNERDtive.Yavapf
try
{
bool exists = false;
foreach (Action<VoiceAttackInvokeProxyClass> action in Contexts)
foreach (Action<VoiceAttackInvokeProxyClass> action in this.Contexts)
{
foreach (ContextAttribute attr in action.Method.GetCustomAttributes<ContextAttribute>())
{
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 dont run the same Action
// multiple times just because it has e.g. multiple
// matching context regexes.
break;
}
}
}
@ -186,21 +309,31 @@ namespace alterNERDtive.Yavapf
}
}
/// <summary>
/// The Exit method, as required by the VoiceAttack plugin API.
/// Runs when VoiceAttack is shut down.
/// </summary>
/// <param name="vaProxy">The VoiceAttack proxy object.</param>
public void VA_Exit1(VoiceAttackProxyClass vaProxy)
{
this.Exit?.Invoke(vaProxy);
}
/// <summary>
/// The StopCommand method, as required by the VoiceAttack plugin API.
/// Runs whenever all commands are stopped using the “Stop All Commands”
/// button or action.
/// </summary>
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)
{
}