Skip to content

Commit

Permalink
✨ Merge "Enhance parameter help" #18
Browse files Browse the repository at this point in the history
  • Loading branch information
decaprime authored Jul 27, 2023
2 parents 9e30cb2 + e182061 commit ecad29a
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 39 deletions.
2 changes: 1 addition & 1 deletion VCF.Core/Basics/BasicAdminCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class BasicAdminCheck : CommandMiddleware
{
public override bool CanExecute(ICommandContext ctx, CommandAttribute cmd, MethodInfo m)
{
Log.Debug($"Running BasicAdmin Check adminOnly: {cmd.AdminOnly} IsAdmin: {ctx.IsAdmin}");
// Log.Debug($"Running BasicAdmin Check adminOnly: {cmd.AdminOnly} IsAdmin: {ctx.IsAdmin}");
return !cmd.AdminOnly || ctx.IsAdmin;
}
}
35 changes: 26 additions & 9 deletions VCF.Core/Basics/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static void HelpCommand(ICommandContext ctx, string search = null)
|| x.Value.Contains(search, StringComparer.InvariantCultureIgnoreCase)
);

individualResults.Where(kvp => CommandRegistry.CanCommandExecute(ctx, kvp.Key));
individualResults = individualResults.Where(kvp => CommandRegistry.CanCommandExecute(ctx, kvp.Key));

if (!individualResults.Any())
{
Expand All @@ -51,7 +51,7 @@ public static void HelpCommand(ICommandContext ctx, string search = null)
var sb = new StringBuilder();
foreach (var command in individualResults)
{
PrintCommandHelp(command.Key, command.Value, sb);
GenerateFullHelp(command.Key, command.Value, sb);
}

ctx.SysPaginatedReply(sb);
Expand All @@ -67,7 +67,7 @@ public static void HelpCommand(ICommandContext ctx, string search = null)
}
ctx.SysPaginatedReply(sb);
}

void PrintAssemblyHelp(ICommandContext ctx, KeyValuePair<Assembly, Dictionary<CommandMetadata, List<string>>> assembly, StringBuilder sb)
{
var name = assembly.Key.GetName().Name;
Expand All @@ -78,20 +78,37 @@ void PrintAssemblyHelp(ICommandContext ctx, KeyValuePair<Assembly, Dictionary<Co

foreach (var command in commands)
{
sb.AppendLine(GenerateHelpText(command));
sb.AppendLine(PrintShortHelp(command));
}
}

void PrintCommandHelp(CommandMetadata command, List<string> aliases, StringBuilder sb)
void GenerateFullHelp(CommandMetadata command, List<string> aliases, StringBuilder sb)
{

sb.AppendLine($"{B(command.Attribute.Name)} ({command.Attribute.Id}) {command.Attribute.Description}");
sb.AppendLine(GenerateHelpText(command));
sb.AppendLine($"Aliases: {string.Join(", ", aliases).Italic()}");
sb.AppendLine(PrintShortHelp(command));
sb.AppendLine($"{B("Aliases").Underline()}: {string.Join(", ", aliases).Italic()}");

// Automatically Display Enum types
var enums = command.Parameters.Select(p => p.ParameterType).Distinct().Where(t => t.IsEnum);
foreach (var e in enums)
{
sb.AppendLine($"{Format.Bold($"{e.Name} Values").Underline()}: {string.Join(", ", Enum.GetNames(e))}");
}

// Check CommandRegistry for types that can be converted and further for IConverterUsage
var converters = command.Parameters.Select(p => p.ParameterType).Distinct().Where(p => CommandRegistry._converters.ContainsKey(p));
foreach (var c in converters)
{
var (obj, _, _) = CommandRegistry._converters[c];
if (obj is not IConverterUsage) continue;
IConverterUsage converterUsage = obj as IConverterUsage;

sb.AppendLine($"{Format.Bold($"{c.Name}")}: {converterUsage.Usage}");
}
}
}

internal static string GenerateHelpText(CommandMetadata command)
internal static string PrintShortHelp(CommandMetadata command)
{
var attr = command.Attribute;
var groupPrefix = string.IsNullOrEmpty(command.GroupAttribute?.Name) ? string.Empty : $"{command.GroupAttribute.Name} ";
Expand Down
37 changes: 34 additions & 3 deletions VCF.Core/Common/Utility.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using RootMotion.FinalIK;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VampireCommandFramework.Common;
Expand Down Expand Up @@ -34,7 +35,7 @@ internal static IEnumerable<string> GetParts(string input)
}
}

internal static void InternalError(this ICommandContext ctx) => ctx.SysReply("An internal error has occured.");
internal static void InternalError(this ICommandContext ctx) => ctx.SysReply("An internal error has occurred.");

internal static void SysReply(this ICommandContext ctx, string input) => ctx.Reply($"[vcf] ".Color(Color.Primary) + input.Color(Color.White));

Expand Down Expand Up @@ -67,10 +68,40 @@ internal static string[] SplitIntoPages(string rawText, int pageSize = MAX_MESSA
{
var pages = new List<string>();
var page = new StringBuilder();
var lines = rawText.Split(Environment.NewLine); // todo: does this work on both platofrms?
var rawLines = rawText.Split(Environment.NewLine); // todo: does this work on both platofrms?
var lines = new List<string>();

// process rawLines -> lines of length <= pageSize
foreach (var line in rawLines)
{
if (line.Length > pageSize)
{
// split into lines of max size preferring to split on spaces
var remaining = line;
while (!string.IsNullOrWhiteSpace(remaining) && remaining.Length > pageSize)
{
// find the last space before the page size within 5% of pageSize buffer
var splitIndex = remaining.LastIndexOf(' ', pageSize - (int)(pageSize * 0.05));
if (splitIndex < 0)
{
splitIndex = Math.Min(pageSize - 1, remaining.Length);
}

lines.Add(remaining.Substring(0, splitIndex));
remaining = remaining.Substring(splitIndex);
}
lines.Add(remaining);
}
else
{
lines.Add(line);
}
}

// batch as many lines together into pageSize
foreach (var line in lines)
{
if (page.Length + line.Length > pageSize)
if ((page.Length + line.Length) > pageSize)
{
pages.Add(page.ToString());
page.Clear();
Expand Down
2 changes: 1 addition & 1 deletion VCF.Core/Framework/ChatCommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ public CommandException Error(string LogMessage)
{
return new CommandException(LogMessage);
}
}
}
2 changes: 1 addition & 1 deletion VCF.Core/Framework/CommandArgumentConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ public abstract class CommandArgumentConverter<T> : CommandArgumentConverter<T,
public abstract class CommandArgumentConverter<T, C> where C : ICommandContext
{
public abstract T Parse(C ctx, string input);
}
}
13 changes: 13 additions & 0 deletions VCF.Core/Framework/IConverterUsage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace VampireCommandFramework;

public interface IConverterUsage
{
/// <summary>
/// Returns a description of the type that this converter can parse. This is used
/// in generated help messages.
/// </summary>
/// <remarks>
/// You are expected to cache this data / make static. This property should be fast to retrieve.
/// </remarks>
public string Usage { get; }
}
22 changes: 13 additions & 9 deletions VCF.Core/Registry/CommandRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ public static class CommandRegistry
{
internal const string DEFAULT_PREFIX = ".";
private static CommandCache _cache = new();
private static Dictionary<Type, (object instance, MethodInfo tryParse, Type contextType)> _converters = new();
/// <summary>
/// From converting type to (object instance, MethodInfo tryParse, Type contextType)
/// </summary>
internal static Dictionary<Type, (object instance, MethodInfo tryParse, Type contextType)> _converters = new();

internal static void Reset()
{
Expand All @@ -30,10 +33,10 @@ internal static void Reset()

internal static bool CanCommandExecute(ICommandContext ctx, CommandMetadata command)
{
Log.Debug($"Executing {Middlewares.Count} CanHandle Middlwares:");
// Log.Debug($"Executing {Middlewares.Count} CanHandle Middlwares:");
foreach (var middleware in Middlewares)
{
Log.Debug($"\t{middleware.GetType().Name}");
// Log.Debug($"\t{middleware.GetType().Name}");
try
{
if (!middleware.CanExecute(ctx, command.Attribute, command.Method))
Expand Down Expand Up @@ -76,7 +79,7 @@ static void HandleAfterExecute(ICommandContext ctx, CommandMetadata command)

foreach (var possible in matchedCommand.PartialMatches)
{
ctx.SysReply(Basics.HelpCommands.GenerateHelpText(possible));
ctx.SysReply(Basics.HelpCommands.PrintShortHelp(possible));
}

return CommandResult.UsageError;
Expand Down Expand Up @@ -123,6 +126,7 @@ static void HandleAfterExecute(ICommandContext ctx, CommandMetadata command)
var param = command.Parameters[i];
var arg = args[i];

// Custom Converter
if (_converters.TryGetValue(param.ParameterType, out var customConverter))
{
var (converter, convertMethod, converterContextType) = customConverter;
Expand Down Expand Up @@ -242,13 +246,13 @@ static void HandleAfterExecute(ICommandContext ctx, CommandMetadata command)
}

HandleAfterExecute(ctx, command);

return CommandResult.Success;
}

public static void UnregisterConverter(Type converter)
{
if(!IsGenericConverterContext(converter) && !IsSpecificConverterContext(converter))
if (!IsGenericConverterContext(converter) && !IsSpecificConverterContext(converter))
{
return;
}
Expand All @@ -260,7 +264,7 @@ public static void UnregisterConverter(Type converter)
Log.Warning($"Could not resolve converter type {converter.Name}");
return;
}

if (_converters.ContainsKey(convertFrom))
{
_converters.Remove(convertFrom);
Expand All @@ -272,8 +276,8 @@ public static void UnregisterConverter(Type converter)
}
}

private static bool IsGenericConverterContext(Type rootType) => rootType.BaseType.Name == typeof(CommandArgumentConverter<>).Name;
private static bool IsSpecificConverterContext(Type rootType) => rootType.BaseType.Name == typeof(CommandArgumentConverter<,>).Name;
internal static bool IsGenericConverterContext(Type rootType) => rootType.BaseType.Name == typeof(CommandArgumentConverter<>).Name;
internal static bool IsSpecificConverterContext(Type rootType) => rootType.BaseType.Name == typeof(CommandArgumentConverter<,>).Name;

public static void RegisterConverter(Type converter)
{
Expand Down
7 changes: 4 additions & 3 deletions VCF.Core/VCF.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>VampireCommandFramework</AssemblyName>
<Description>My first plugin</Description>
<Description>Framework for commands in V Rising</Description>
<Version>0.0.999</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
Expand All @@ -13,14 +13,15 @@
<PackageId>VampireCommandFramework</PackageId>
<RootNamespace>VampireCommandFramework</RootNamespace>
<Authors>deca</Authors>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<!-- Copy VampireCommandFramework.dll to /dist -->
<Target Name="Thunderstore Copy to Dist" AfterTargets="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
<Copy SourceFiles="$(OutDir)\VampireCommandFramework.dll" DestinationFolder="$(SolutionDir)/dist" />
</Target>
<ItemGroup>
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be*" IncludeAssets="compile" />
<PackageReference Include="BepInEx.Core" Version="6.0.0-be*" IncludeAssets="compile" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.668" IncludeAssets="compile" />
<PackageReference Include="BepInEx.Core" Version="6.0.0-be.668" IncludeAssets="compile" />
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" />
<PackageReference Include="VRising.Unhollowed.Client" Version="0.6.5.*" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
Expand Down
7 changes: 6 additions & 1 deletion VCF.Tests/AssertReplyContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ public void AssertReply(string expected)
{
Assert.That(_sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()), Is.EqualTo(expected));
}
public void AssertReplyContains(string expected)
{
var repliedText = _sb.ToString().TrimEnd(Environment.NewLine.ToCharArray());
Assert.That(repliedText.Contains(expected), Is.True, $"Expected {expected} to be contained in replied: {repliedText}");
}

public void AssertInternalError()
{
Assert.That(_sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()), Is.EqualTo("[vcf] An internal error has occured."));
Assert.That(_sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()), Is.EqualTo("[vcf] An internal error has occurred."));
}
}
16 changes: 8 additions & 8 deletions VCF.Tests/CommandArgumentConverterTests.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
using FakeItEasy;
using NUnit.Framework;
using VampireCommandFramework;
using VampireCommandFramework.Basics;

namespace VCF.Tests;
public class CommandArgumentConverterTests
{
public record SomeType() { readonly string Unique = Any.String(); };
record SomeType() { readonly string Unique = Any.String(); };

public static readonly SomeType ReturnedFromGeneric = new(), ReturnedFromSpecific = new(), DefaultValue = new();
static readonly SomeType ReturnedFromGeneric = new(), ReturnedFromSpecific = new(), DefaultValue = new();

public class GenericContextConverter : CommandArgumentConverter<SomeType>
class GenericContextConverter : CommandArgumentConverter<SomeType>
{
public override SomeType Parse(ICommandContext ctx, string input)
{
return ReturnedFromGeneric;
}
}

public class SpecificContextConverter : CommandArgumentConverter<SomeType, SecondaryContext>
class SpecificContextConverter : CommandArgumentConverter<SomeType, SecondaryContext>
{
public override SomeType Parse(SecondaryContext ctx, string input)
{
return ReturnedFromSpecific;
}
}

public class GenericContextTestCommands
class GenericContextTestCommands
{
[Command("test")]
public void TestCommand(ICommandContext ctx, SomeType value) { }
Expand All @@ -34,7 +35,7 @@ public void TestCommand(ICommandContext ctx, SomeType value) { }
public void TestWithefault(ICommandContext ctx, SomeType value = null) { }
}

public class SecondaryContext : ICommandContext
internal class SecondaryContext : ICommandContext
{
public IServiceProvider Services => throw new NotImplementedException();

Expand Down Expand Up @@ -103,7 +104,7 @@ public void UnregisterConverter_RemovesConverter()
{
CommandRegistry.RegisterConverter(typeof(GenericContextConverter));
CommandRegistry.RegisterCommandType(typeof(GenericContextTestCommands));

var ctx = new SecondaryContext();
Assert.That(CommandRegistry.Handle(ctx, ".test something"), Is.EqualTo(CommandResult.Success));

Expand Down Expand Up @@ -163,5 +164,4 @@ public void CanConvert_SpecificContext_WithDefault()
Assert.That(CommandRegistry.Handle(ctx, ".test-default"), Is.EqualTo(CommandResult.Success));
Assert.That(CommandRegistry.Handle(ctx, ".test-default something"), Is.EqualTo(CommandResult.Success));
}

}
Loading

0 comments on commit ecad29a

Please sign in to comment.