diff --git a/README.NuGet.md b/README.NuGet.md
index e7ebdd1..518cea5 100644
--- a/README.NuGet.md
+++ b/README.NuGet.md
@@ -27,7 +27,8 @@ Flandre 自设计之初就是为了跨平台,对聊天平台的结构进行抽
目前已经实现的适配器:
-- [Flandre.Adapters.Konata](https://github.com/FlandreDevs/Flandre/blob/dev/src/Flandre.Adapters.Konata/README.md) - QQ 协议适配,基于 [Konata.Core](https://github.com/KonataDev/Konata.Core)
+- [Flandre.Adapters.Konata](https://github.com/FlandreDevs/Flandre/blob/dev/src/Flandre.Adapters.Konata/README.md) - QQ
+ 协议适配,基于 [Konata.Core](https://github.com/KonataDev/Konata.Core)
### 指令系统
@@ -100,13 +101,15 @@ class ExamplePlugin2 : Plugin
}
```
-这个插件包含一条有两个参数的指令,类型都为 `string`,其中 `foo` 为必选参数,`bar` 为可选参数。如果调用指令时未提供可选参数,参数将被初始化为类型默认值;如果为提供必选参数,bot 将向其发送一条提示信息并停止执行指令。
+这个插件包含一条有两个参数的指令,类型都为 `string`,其中 `foo` 为必选参数,`bar`
+为可选参数。如果调用指令时未提供可选参数,参数将被初始化为类型默认值;如果为提供必选参数,bot 将向其发送一条提示信息并停止执行指令。
向 bot 发送 `example qwq ovo`(~~随便什么~~),bot 会将参数的值发送回来。
### 类型约束
-如果我们不对指令的参数进行类型约束,那么参数的类型将默认为 `string`。如要添加参数,可以在参数名称后添加 `:` 号和类型名称。类型名称支持 C# 中绝大多数的基本类型,如 `int`, `double`, `long`, `bool` 等等,在解析过程中会自动进行类型检查和转换。
+如果我们不对指令的参数进行类型约束,那么参数的类型将默认为 `string`。如要添加参数,可以在参数名称后添加 `:` 号和类型名称。类型名称支持
+C# 中绝大多数的基本类型,如 `int`, `double`, `long`, `bool` 等等,在解析过程中会自动进行类型检查和转换。
举个例子:
@@ -132,7 +135,8 @@ public MessageContent? OnExample(MessageContext ctx, ParsedArgs args)
[Command("example [foo:int=1145] [bar:bool=true]")]
```
-如果不人为指定默认值,参数将被初始化为 C# 中的类型默认值(即 `default(T)`)。`string` 比较特殊,在参数中它的默认值是空字符串,而不是 `null`。
+如果不人为指定默认值,参数将被初始化为 C# 中的类型默认值(即 `default(T)`)。`string`
+比较特殊,在参数中它的默认值是空字符串,而不是 `null`。
### 灵活的表现形式
diff --git a/src/Flandre.Core/Attributes/CommandAttribute.cs b/src/Flandre.Core/Attributes/CommandAttribute.cs
index 55ffd9c..1c7c93b 100644
--- a/src/Flandre.Core/Attributes/CommandAttribute.cs
+++ b/src/Flandre.Core/Attributes/CommandAttribute.cs
@@ -32,7 +32,7 @@ public CommandAttribute(string pattern)
while (!parser.IsEnd())
{
- var bracket = parser.Current();
+ var bracket = parser.Current;
if (!"<[".Contains(bracket))
{
parser.Read(' ');
diff --git a/src/Flandre.Core/Attributes/OptionAttribute.cs b/src/Flandre.Core/Attributes/OptionAttribute.cs
index 50bb38c..1560451 100644
--- a/src/Flandre.Core/Attributes/OptionAttribute.cs
+++ b/src/Flandre.Core/Attributes/OptionAttribute.cs
@@ -13,6 +13,11 @@ public class OptionAttribute : Attribute
///
public string Name { get; }
+ ///
+ /// 短名称
+ ///
+ public char? ShortName { get; }
+
///
/// 选项别名
///
@@ -45,20 +50,27 @@ public OptionAttribute(string name, string? pattern = null)
if (parser.SkipSpaces().IsEnd()) return;
var first = parser.Read(' ');
+ ParameterInfo info;
- if (first.StartsWith('-'))
+ if (first.StartsWith("--"))
{
Alias = first.TrimStart('-');
if (parser.SkipSpaces().IsEnd()) return;
- var info = CommandUtils.ParseParameterSection(parser.ReadToEnd(), cmdName: Name);
- Type = info.Type;
- DefaultValue = info.DefaultValue;
+ info = CommandUtils.ParseParameterSection(parser.ReadToEnd(), cmdName: Name);
+ }
+ else if (first.StartsWith('-'))
+ {
+ var trimmed = first.TrimStart('-');
+ ShortName = trimmed.Length > 0 ? trimmed[0] : null;
+ if (parser.SkipSpaces().IsEnd()) return;
+ info = CommandUtils.ParseParameterSection(parser.ReadToEnd(), cmdName: Name);
}
else
{
- var info = CommandUtils.ParseParameterSection(first, cmdName: Name);
- Type = info.Type;
- DefaultValue = info.DefaultValue;
+ info = CommandUtils.ParseParameterSection(first, cmdName: Name);
}
+
+ Type = info.Type;
+ DefaultValue = info.DefaultValue;
}
}
\ No newline at end of file
diff --git a/src/Flandre.Core/Common/ArgumentManager.cs b/src/Flandre.Core/Common/ArgumentManager.cs
index 977b3ad..5e00602 100644
--- a/src/Flandre.Core/Common/ArgumentManager.cs
+++ b/src/Flandre.Core/Common/ArgumentManager.cs
@@ -47,28 +47,4 @@ public T Get(string name)
{
return (T)ArgumentList.First(arg => arg.Key == name).Value;
}
-
- ///
- /// 根据索引获取参数
- ///
- /// 参数索引
- /// 返回类型
- /// 参数值,若索引越界或无法转换则返回 default(T)
- public T? GetOrDefault(int index)
- {
- var result = ArgumentList.ElementAtOrDefault(index).Value;
- return result is T casted ? casted : default;
- }
-
- ///
- /// 根据名称获取参数
- ///
- /// 参数名称
- /// 返回类型
- /// 参数值,若未找到或无法转换则返回 default(T)
- public T? GetOrDefault(string name)
- {
- var result = ArgumentList.FirstOrDefault(arg => arg.Key == name).Value;
- return result is T casted ? casted : default;
- }
}
\ No newline at end of file
diff --git a/src/Flandre.Core/Common/Command.cs b/src/Flandre.Core/Common/Command.cs
index f60d1c2..15219ac 100644
--- a/src/Flandre.Core/Common/Command.cs
+++ b/src/Flandre.Core/Common/Command.cs
@@ -1,5 +1,7 @@
using System.Reflection;
using Flandre.Core.Attributes;
+using Flandre.Core.Messaging;
+using Flandre.Core.Utils;
namespace Flandre.Core.Common;
@@ -23,10 +25,153 @@ public class Command
///
public List Options { get; }
- internal Command(CommandAttribute info, MethodInfo innerMethod, List options)
+ private readonly Logger _pluginLogger;
+
+ internal Command(CommandAttribute info, MethodInfo innerMethod, List options, Logger pluginLogger)
{
CommandInfo = info;
InnerMethod = innerMethod;
Options = options;
+ _pluginLogger = pluginLogger;
+ }
+
+ internal MessageContent? ParseCommand(MessageContext ctx, StringParser parser)
+ {
+ var args = new ParsedArgs();
+
+ var argIndex = 0;
+ var providedArgs = new List();
+
+ while (!parser.IsEnd())
+ {
+ var peek = parser.SkipSpaces().Peek(' ');
+
+ if (peek.StartsWith("--"))
+ {
+ // option (full)
+ var optName = parser.Read(' ').TrimStart('-');
+ var optNo = false;
+
+ if (optName.Length > 3 && optName.StartsWith("no-"))
+ {
+ optName = optName[3..];
+ optNo = true;
+ }
+
+ var option = Options.FirstOrDefault(opt => opt.Alias == optName)
+ ?? Options.FirstOrDefault(opt => opt.Name == optName);
+ if (option is null)
+ return $"未知选项:{optName}。";
+
+ parser.SkipSpaces();
+
+ switch (option.Type)
+ {
+ case "bool":
+ args.Options.OptionsDict[option.Name] = !optNo;
+ break;
+
+ case "string":
+ args.Options.OptionsDict[option.Name] = parser.ReadQuoted();
+ break;
+
+ default:
+ if (CommandUtils.TryParseType(parser.Read(' '),
+ option.Type, out var result, false))
+ args.Options.OptionsDict[option.Name] = result;
+ else return $"选项 {option.Name} 类型错误,应为 {option.Type}。";
+ break;
+ }
+ }
+ else if (peek.StartsWith('-'))
+ {
+ // option (short)
+ var opts = parser.Read(' ').TrimStart('-');
+
+ parser.SkipSpaces();
+
+ for (var i = 0; i < opts.Length; i++)
+ {
+ var optName = opts[i];
+ var option = Options.FirstOrDefault(opt => opt.ShortName == optName);
+ if (option is null)
+ return $"未知选项:{optName}。";
+
+ if (option.Type == "bool")
+ {
+ args.Options.OptionsDict[option.Name] = true;
+ }
+ else
+ {
+ if (i < opts.Length - 1)
+ return $"选项 {option.Name} 类型错误,应为 {option.Type}。";
+
+ if (option.Type == "string")
+ args.Options.OptionsDict[option.Name] = parser.ReadQuoted();
+ else if (CommandUtils.TryParseType(parser.Read(' '),
+ option.Type, out var result, false))
+ args.Options.OptionsDict[option.Name] = result;
+ else return $"选项 {option.Name} 类型错误,应为 {option.Type}。";
+ }
+ }
+ }
+ else
+ {
+ // argument
+ if (argIndex >= CommandInfo.Parameters.Count)
+ return "参数过多,请检查指令格式。";
+
+ var param = CommandInfo.Parameters[argIndex];
+
+ if (param.Type == "string")
+ {
+ args.Arguments.ArgumentList.Add(
+ new KeyValuePair(param.Name, parser.ReadQuoted()));
+ }
+ else
+ {
+ if (CommandUtils.TryParseType(parser.Read(' '),
+ param.Type, out var result, false))
+ args.Arguments.ArgumentList.Add(new KeyValuePair(param.Name, result));
+ else return $"参数 {param.Name} 类型错误,应为 {param.Type}。";
+ }
+
+ providedArgs.Add(param.Name);
+ argIndex++;
+ }
+ }
+
+ // 默认值
+ foreach (var param in CommandInfo.Parameters)
+ {
+ var provided = providedArgs.Contains(param.Name);
+ if (param.IsRequired && !provided)
+ return $"参数 {param.Name} 缺失。";
+ if (param.IsRequired || provided) continue;
+ args.Arguments.ArgumentList.Add(new KeyValuePair(param.Name, param.DefaultValue));
+ }
+
+ foreach (var opt in Options)
+ if (!args.Options.OptionsDict.ContainsKey(opt.Name))
+ args.Options.OptionsDict[opt.Name] = opt.DefaultValue;
+
+ try
+ {
+ var cmdResult = InnerMethod.Invoke(
+ this, new object[] { ctx, args }[..InnerMethod.GetParameters().Length]);
+ var content = cmdResult as MessageContent ?? (cmdResult as Task)?.Result ?? null;
+
+ return content;
+ }
+ catch (TargetInvocationException te)
+ {
+ _pluginLogger.Error(te.InnerException ?? te);
+ }
+ catch (Exception e)
+ {
+ _pluginLogger.Error(e);
+ }
+
+ return null;
}
}
\ No newline at end of file
diff --git a/src/Flandre.Core/Common/OptionManager.cs b/src/Flandre.Core/Common/OptionManager.cs
index 269d7c8..35e17e6 100644
--- a/src/Flandre.Core/Common/OptionManager.cs
+++ b/src/Flandre.Core/Common/OptionManager.cs
@@ -15,17 +15,14 @@ public class OptionManager : IEnumerable>
/// 选项名称
/// 返回类型
/// 若未提供该选项,或类型错误则返回类型默认值
- public T? GetOrDefault(string key)
+ public T Get(string key)
{
- var value = OptionsDict.GetValueOrDefault(key);
- return value is not null ? (T)value : default;
+ return (T)OptionsDict[key];
}
///
/// 获取 Enumerator
///
- ///
- ///
public IEnumerator> GetEnumerator()
{
return OptionsDict.GetEnumerator();
diff --git a/src/Flandre.Core/Common/ParsedArgs.cs b/src/Flandre.Core/Common/ParsedArgs.cs
index 1a9b355..9eab59b 100644
--- a/src/Flandre.Core/Common/ParsedArgs.cs
+++ b/src/Flandre.Core/Common/ParsedArgs.cs
@@ -36,24 +36,12 @@ public T GetArgument(string name)
}
///
- /// 根据索引获取参数
- ///
- /// 参数索引
- /// 返回类型
- /// 参数值,若索引越界或无法转换则返回 default(T)
- public T? GetArgumentOrDefault(int index)
- {
- return Arguments.GetOrDefault(index);
- }
-
- ///
- /// 根据名称获取参数
+ /// 根据名称获取选项,值将被强制转换为 T 类型
///
/// 参数名称
/// 返回类型
- /// 参数值,若未找到或无法转换则返回 default(T)
- public T? GetArgumentOrDefault(string name)
+ public T GetOption(string name)
{
- return Arguments.GetOrDefault(name);
+ return Options.Get(name);
}
}
\ No newline at end of file
diff --git a/src/Flandre.Core/Common/Plugin.cs b/src/Flandre.Core/Common/Plugin.cs
index 2103361..d89522f 100644
--- a/src/Flandre.Core/Common/Plugin.cs
+++ b/src/Flandre.Core/Common/Plugin.cs
@@ -38,141 +38,17 @@ public Plugin()
foreach (var method in type.GetMethods())
{
var attr = method.GetCustomAttribute();
- if (attr is not null)
- {
- var options = new List();
+ if (attr is null) continue;
- foreach (var optionAttr in method.GetCustomAttributes())
- options.Add(optionAttr);
+ var options = method.GetCustomAttributes().ToList();
- Commands.Add(new Command(attr, method, options));
- }
+ Commands.Add(new Command(attr, method, options, Logger));
}
}
- internal MessageContent? OnCommandParsing(MessageContext ctx)
+ internal MessageContent GetHelp()
{
- var commandStr = ctx.Message.GetText();
- if (string.IsNullOrWhiteSpace(commandStr)) return null;
-
- foreach (var command in Commands)
- {
- var basePattern = ctx.App.Config.CommandPrefix +
- (PluginInfo.BaseCommand + ' ' +
- command.CommandInfo.Command).TrimStart();
-
- var startsWithFlag = commandStr.StartsWith(basePattern);
-
- if (!startsWithFlag)
- continue;
-
- if (startsWithFlag)
- commandStr = commandStr[basePattern.Length..].Trim();
-
- var (parsedArgs, errorMsg) = ParseCommand(command, commandStr);
- if (errorMsg != null)
- return errorMsg;
-
- var result = command.InnerMethod.Invoke(
- this, new object[] { ctx, parsedArgs }[..command.InnerMethod.GetParameters().Length]);
- var content = result as MessageContent ?? (result as Task)?.Result ?? null;
- return content;
- }
-
- return null;
- }
-
- internal (ParsedArgs, string?) ParseCommand(Command command, string source)
- {
- var args = new ParsedArgs();
- var parser = new StringParser(source);
-
- var argIndex = 0;
- var providedArgs = new List();
-
- while (!parser.IsEnd())
- {
- var peek = parser.SkipSpaces().Peek(' ');
-
- if (peek.StartsWith('-'))
- {
- // option
- var optName = parser.Read(' ').TrimStart('-');
- var optNo = false;
-
- if (optName.Length > 3 && optName.StartsWith("no-"))
- {
- optName = optName[3..];
- optNo = true;
- }
-
- var option = command.Options.FirstOrDefault(opt => opt.Alias == optName)
- ?? command.Options.FirstOrDefault(opt => opt.Name == optName);
- if (option is null)
- return (args, $"未知选项:{optName}。");
-
- parser.SkipSpaces();
-
- switch (option.Type)
- {
- case "bool":
- args.Options.OptionsDict[option.Name] = !optNo;
- break;
-
- case "string":
- args.Options.OptionsDict[option.Name] = parser.ReadQuoted();
- break;
-
- default:
- if (CommandUtils.TryParseType(parser.Read(' '),
- option.Type, out var result, false))
- args.Options.OptionsDict[option.Name] = result;
- else return (args, $"选项 {option.Name} 类型错误,应为 {option.Type}。");
- break;
- }
- }
- else
- {
- // argument
- if (argIndex >= command.CommandInfo.Parameters.Count)
- return (args, "参数过多,请检查指令格式。");
-
- var param = command.CommandInfo.Parameters[argIndex];
-
- if (param.Type == "string")
- {
- var quote = parser.Peek(1);
- args.Arguments.ArgumentList.Add(
- new KeyValuePair(param.Name, parser.ReadQuoted()));
- }
- else
- {
- if (CommandUtils.TryParseType(parser.Read(' '),
- param.Type, out var result, false))
- args.Arguments.ArgumentList.Add(new KeyValuePair(param.Name, result));
- else return (args, $"参数 {param.Name} 类型错误,应为 {param.Type}。");
- }
-
- providedArgs.Add(param.Name);
- argIndex++;
- }
- }
-
- // 默认值
- foreach (var param in command.CommandInfo.Parameters)
- {
- var provided = providedArgs.Contains(param.Name);
- if (param.IsRequired && !provided)
- return (args, $"参数 {param.Name} 缺失。");
- if (param.IsRequired || provided) continue;
- args.Arguments.ArgumentList.Add(new KeyValuePair(param.Name, param.DefaultValue));
- }
-
- foreach (var opt in command.Options)
- if (!args.Options.OptionsDict.ContainsKey(opt.Name))
- args.Options.OptionsDict[opt.Name] = opt.DefaultValue;
-
- return (args, null);
+ throw new NotImplementedException();
}
///
diff --git a/src/Flandre.Core/Flandre.Core.csproj b/src/Flandre.Core/Flandre.Core.csproj
index adbadc8..298fd85 100644
--- a/src/Flandre.Core/Flandre.Core.csproj
+++ b/src/Flandre.Core/Flandre.Core.csproj
@@ -23,8 +23,8 @@
-
-
+
+
diff --git a/src/Flandre.Core/FlandreApp.cs b/src/Flandre.Core/FlandreApp.cs
index 94dbfa3..9c67869 100644
--- a/src/Flandre.Core/FlandreApp.cs
+++ b/src/Flandre.Core/FlandreApp.cs
@@ -1,8 +1,7 @@
-using System.Reflection;
+using System.Runtime.CompilerServices;
using Flandre.Core.Common;
using Flandre.Core.Events.App;
using Flandre.Core.Utils;
-using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Flandre.Core.Tests")]
[assembly: InternalsVisibleTo("Flandre.TestKit")]
@@ -26,6 +25,8 @@ public class FlandreApp
private readonly CancellationTokenSource _appStopTokenSource = new();
+ private readonly Dictionary _commandMap = new();
+
internal static Logger Logger { get; } = new("App");
///
@@ -83,6 +84,22 @@ public FlandreApp Use(IModule module)
case Plugin plugin:
Plugins.Add(plugin);
+
+ if (plugin.PluginInfo.BaseCommand is not null)
+ {
+ _commandMap[plugin.PluginInfo.BaseCommand] = plugin;
+ foreach (var command in plugin.Commands)
+ if (plugin.PluginInfo.BaseCommand == command.CommandInfo.Command)
+ _commandMap[command.CommandInfo.Command] = command;
+ else
+ _commandMap[$"{plugin.PluginInfo.BaseCommand}.{command.CommandInfo.Command}"] = command;
+ }
+ else
+ {
+ foreach (var command in plugin.Commands)
+ _commandMap[command.CommandInfo.Command] = command;
+ }
+
break;
}
@@ -128,45 +145,89 @@ public void Stop()
private void SubscribeEvents()
{
foreach (var bot in Bots)
- foreach (var plugin in Plugins)
{
+ foreach (var plugin in Plugins)
+ {
+ var ctx = new Context(this, bot);
+
+ bot.OnMessageReceived += (_, e) =>
+ plugin.OnMessageReceived(new MessageContext(this, bot, e.Message));
+ bot.OnGuildInvited += (_, e) => plugin.OnGuildInvited(ctx, e);
+ bot.OnGuildRequested += (_, e) => plugin.OnGuildRequested(ctx, e);
+ bot.OnFriendRequested += (_, e) => plugin.OnFriendRequested(ctx, e);
+ }
+
bot.OnMessageReceived += (_, e) =>
+ OnCommandParsing(new MessageContext(this, bot, e.Message));
+ }
+ }
+
+ private void OnCommandParsing(MessageContext ctx)
+ {
+ var commandStr = ctx.Message.GetText().Trim();
+
+ if (commandStr.StartsWith(Config.CommandPrefix))
+ {
+ void DealCommand(Command cmd, StringParser p)
{
- var ctx = new MessageContext(this, bot, e.Message);
- try
- {
- plugin.OnMessageReceived(ctx);
- var content = plugin.OnCommandParsing(ctx);
- if (content is not null)
- bot.SendMessage(e.Message.SourceType,
- e.Message.GuildId, e.Message.ChannelId, e.Message.Sender.Id, content);
- }
- catch (TargetInvocationException exception)
- {
- plugin.Logger.Error(exception.InnerException ?? exception);
- }
- catch (Exception ex)
- {
- plugin.Logger.Error(ex);
- }
- };
+ var content = cmd.ParseCommand(ctx, p);
+ if (content is null) return;
+ ctx.Bot.SendMessage(ctx.Message.SourceType, ctx.Message.GuildId, ctx.Message.ChannelId,
+ ctx.Message.Sender.Id, content);
+ }
+
+ if (commandStr == Config.CommandPrefix) return;
+ var parser = new StringParser(commandStr.TrimStart(Config.CommandPrefix));
+ var root = parser.Read(' ');
+
+ var obj = _commandMap.GetValueOrDefault(root);
+
+ switch (obj)
+ {
+ case null:
+ if (Config.CommandPrefix == "") return;
+ ctx.Bot.SendMessage(ctx.Message.SourceType, ctx.Message.GuildId, ctx.Message.ChannelId,
+ ctx.Message.Sender.Id, $"未找到指令:{root}。");
+ return;
- var ctx = new Context(this, bot);
+ case Command command:
+ DealCommand(command, parser);
+ return;
- bot.OnGuildInvited += (_, e) => plugin.OnGuildInvited(ctx, e);
- bot.OnGuildRequested += (_, e) => plugin.OnGuildRequested(ctx, e);
- bot.OnFriendRequested += (_, e) => plugin.OnFriendRequested(ctx, e);
+ case Plugin plugin:
+ {
+ if (!parser.IsEnd())
+ {
+ root = $"{root}.{parser.Read(' ')}";
+ obj = _commandMap.GetValueOrDefault(root);
+ switch (obj)
+ {
+ case null:
+ ctx.Bot.SendMessage(ctx.Message.SourceType, ctx.Message.GuildId, ctx.Message.ChannelId,
+ ctx.Message.Sender.Id, $"未找到指令:{root}。");
+ return;
+ case Command cmd:
+ DealCommand(cmd, parser);
+ return;
+ }
+ }
+
+ ctx.Bot.SendMessage(ctx.Message.SourceType, ctx.Message.GuildId, ctx.Message.ChannelId,
+ ctx.Message.Sender.Id, plugin.GetHelp());
+ break;
+ }
+ }
}
}
-}
-///
-/// 应用配置
-///
-public class AppConfig
-{
///
- /// 全局指令前缀
+ /// 应用配置
///
- public string CommandPrefix { get; set; } = "";
+ public class AppConfig
+ {
+ ///
+ /// 全局指令前缀
+ ///
+ public string CommandPrefix { get; set; } = "";
+ }
}
\ No newline at end of file
diff --git a/src/Flandre.Core/Models/GuildMember.cs b/src/Flandre.Core/Models/GuildMember.cs
index b0ed8e8..9c22eb5 100644
--- a/src/Flandre.Core/Models/GuildMember.cs
+++ b/src/Flandre.Core/Models/GuildMember.cs
@@ -8,5 +8,5 @@ public class GuildMember : User
///
/// 成员角色
///
- public List Roles { get; init; } = new ();
+ public List Roles { get; init; } = new();
}
\ No newline at end of file
diff --git a/src/Flandre.Core/Utils/CommandUtils.cs b/src/Flandre.Core/Utils/CommandUtils.cs
index 6b53bab..0b19d75 100644
--- a/src/Flandre.Core/Utils/CommandUtils.cs
+++ b/src/Flandre.Core/Utils/CommandUtils.cs
@@ -9,7 +9,7 @@ internal static ParameterInfo ParseParameterSection(string section, string defau
{
var info = new ParameterInfo();
section = section.Trim();
-
+
if (section[0] == '<')
info.IsRequired = true;
@@ -21,7 +21,7 @@ internal static ParameterInfo ParseParameterSection(string section, string defau
info.DefaultValue = GetTypeDefaultValue(info.Type, defaultType);
// 默认值
- if (!info.IsRequired && innerRight.Length > 1)
+ if (innerRight.Length > 1)
{
if (TryParseType(innerRight[1].Trim(), info.Type, out var result))
info.DefaultValue = result;
diff --git a/src/Flandre.Core/Utils/Logger.cs b/src/Flandre.Core/Utils/Logger.cs
index 9600583..dbb74b7 100644
--- a/src/Flandre.Core/Utils/Logger.cs
+++ b/src/Flandre.Core/Utils/Logger.cs
@@ -18,7 +18,7 @@ public Logger(string name)
{
Name = name;
}
-
+
private static void Log(string message)
{
var logMessage = $"{DateTime.Now:HH:mm:ss} {message}";
diff --git a/src/Flandre.Core/Utils/StringParser.cs b/src/Flandre.Core/Utils/StringParser.cs
index 1db36a2..449cfcd 100644
--- a/src/Flandre.Core/Utils/StringParser.cs
+++ b/src/Flandre.Core/Utils/StringParser.cs
@@ -5,6 +5,8 @@ internal class StringParser
private readonly string _str;
private int _pos;
+ internal char Current => _str[_pos];
+
internal StringParser(string str)
{
_str = str;
@@ -32,11 +34,6 @@ internal StringParser SkipSpaces()
return this;
}
- internal char Current()
- {
- return _str[_pos];
- }
-
internal string Peek(int length)
{
return _str.Substring(_pos, length);
diff --git a/src/Flandre.Core/Utils/TextUtils.cs b/src/Flandre.Core/Utils/TextUtils.cs
index 984716b..b0c14f0 100644
--- a/src/Flandre.Core/Utils/TextUtils.cs
+++ b/src/Flandre.Core/Utils/TextUtils.cs
@@ -6,4 +6,27 @@ internal static string RemoveString(this string text, string remove)
{
return text.Replace(remove, "");
}
+
+ internal static string TrimStart(this string source, string value,
+ StringComparison comparison = StringComparison.Ordinal)
+ {
+ if (value == "") return source;
+ var valueLength = value.Length;
+ var startIndex = 0;
+ while (source.IndexOf(value, startIndex, comparison) == startIndex) startIndex += valueLength;
+
+ return source[startIndex..];
+ }
+
+ internal static string TrimEnd(this string source, string value,
+ StringComparison comparison = StringComparison.Ordinal)
+ {
+ if (value == "") return source;
+ var sourceLength = source.Length;
+ var valueLength = value.Length;
+ var count = sourceLength;
+ while (source.LastIndexOf(value, count, comparison) == count - valueLength) count -= valueLength;
+
+ return source[..count];
+ }
}
\ No newline at end of file
diff --git a/src/Flandre.TestKit/Extensions.cs b/src/Flandre.TestKit/Extensions.cs
index ba301e1..fdbdf06 100644
--- a/src/Flandre.TestKit/Extensions.cs
+++ b/src/Flandre.TestKit/Extensions.cs
@@ -17,8 +17,10 @@ public static FlandreTestClient GenerateChannelClient(this TestAdapter adapter,
}
public static FlandreTestClient GenerateChannelClient(this TestAdapter adapter)
- => GenerateChannelClient(adapter, Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
+ {
+ return GenerateChannelClient(adapter, Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
Guid.NewGuid().ToString());
+ }
public static FlandreTestClient GenerateFriendClient(this TestAdapter adapter, string userId)
{
@@ -30,5 +32,7 @@ public static FlandreTestClient GenerateFriendClient(this TestAdapter adapter, s
}
public static FlandreTestClient GenerateFriendClient(this TestAdapter adapter)
- => GenerateFriendClient(adapter, Guid.NewGuid().ToString());
+ {
+ return GenerateFriendClient(adapter, Guid.NewGuid().ToString());
+ }
}
\ No newline at end of file
diff --git a/src/Flandre.TestKit/Flandre.TestKit.csproj b/src/Flandre.TestKit/Flandre.TestKit.csproj
index c4b3739..28d1f85 100644
--- a/src/Flandre.TestKit/Flandre.TestKit.csproj
+++ b/src/Flandre.TestKit/Flandre.TestKit.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/src/Flandre.TestKit/TestAdapter.cs b/src/Flandre.TestKit/TestAdapter.cs
index fbb2634..fab7c94 100644
--- a/src/Flandre.TestKit/TestAdapter.cs
+++ b/src/Flandre.TestKit/TestAdapter.cs
@@ -7,7 +7,7 @@ namespace Flandre.TestKit;
public class TestAdapter : IAdapter
{
internal readonly TestBot Bot = new();
-
+
public async Task Start()
{
}
diff --git a/src/Flandre.TestKit/TestBot.cs b/src/Flandre.TestKit/TestBot.cs
index 2e24368..020993c 100644
--- a/src/Flandre.TestKit/TestBot.cs
+++ b/src/Flandre.TestKit/TestBot.cs
@@ -60,7 +60,7 @@ public async Task GetSelf()
{
Name = "Test Bot",
Nickname = "Test Bot",
- Id = _selfId,
+ Id = _selfId
};
}
diff --git a/tests/Flandre.Core.Tests/CommonTests/PluginTests.cs b/tests/Flandre.Core.Tests/CommonTests/PluginTests.cs
index 9469683..0dd8d81 100644
--- a/tests/Flandre.Core.Tests/CommonTests/PluginTests.cs
+++ b/tests/Flandre.Core.Tests/CommonTests/PluginTests.cs
@@ -2,5 +2,4 @@
public class PluginTests
{
-
}
\ No newline at end of file
diff --git a/tests/Flandre.Core.Tests/Flandre.Core.Tests.csproj b/tests/Flandre.Core.Tests/Flandre.Core.Tests.csproj
index 0905195..ba6b98b 100644
--- a/tests/Flandre.Core.Tests/Flandre.Core.Tests.csproj
+++ b/tests/Flandre.Core.Tests/Flandre.Core.Tests.csproj
@@ -9,8 +9,8 @@
-
-
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/tests/Flandre.Core.Tests/FlandreAppTests.cs b/tests/Flandre.Core.Tests/FlandreAppTests.cs
index 66639f8..3441d3b 100644
--- a/tests/Flandre.Core.Tests/FlandreAppTests.cs
+++ b/tests/Flandre.Core.Tests/FlandreAppTests.cs
@@ -3,6 +3,8 @@
using Flandre.Core.Messaging;
using Flandre.TestKit;
+// ReSharper disable StringLiteralTypo
+
namespace Flandre.Core.Tests;
public class FlandreAppTests
@@ -18,18 +20,24 @@ public void TestFlandreApp()
app.OnAppReady += async (_, _) =>
{
- var content = await channelClient.SendForReply("114514");
- Assert.Equal("114514", content?.GetText());
-
+ var content = await channelClient.SendForReply("OMR:114514");
+ Assert.Equal("OMR:114514", content?.GetText());
+
content = await friendClient.SendForReply("test1 true --opt 114.514");
- Assert.Equal("arg1: True opt: 114.514", content?.GetText());
-
+ Assert.Equal("arg1: True opt: 114.514 b: False t: True",
+ content?.GetText());
+
content = await friendClient.SendForReply("test1 -o 1919.810 false");
- Assert.Equal("arg1: False opt: 1919.81", content?.GetText());
+ Assert.Equal("arg1: False opt: 1919.81 b: False t: True",
+ content?.GetText());
+
+ content = await friendClient.SendForReply("test1 -bo 111.444 --no-trueopt false");
+ Assert.Equal("arg1: False opt: 111.444 b: True t: False",
+ content?.GetText());
app.Stop();
};
-
+
app.Use(adapter).Use(new TestPlugin()).Start();
}
}
@@ -39,15 +47,20 @@ public class TestPlugin : Plugin
{
public override void OnMessageReceived(MessageContext ctx)
{
- ctx.Bot.SendMessage(ctx.Message);
+ if (ctx.Message.GetText().StartsWith("OMR:"))
+ ctx.Bot.SendMessage(ctx.Message);
}
[Command("test1 ")]
[Option("opt", "-o ")]
+ [Option("boolopt", "-b <:bool>")]
+ [Option("trueopt", "-t <:bool=true>")]
public static MessageContent OnTest1(MessageContext ctx, ParsedArgs args)
{
var arg1 = args.GetArgument("arg1");
- var opt = args.Options.GetOrDefault("opt");
- return $"arg1: {arg1} opt: {opt}";
+ var opt = args.GetOption("opt");
+ var boolOpt = args.GetOption("boolopt");
+ var trueOpt = args.GetOption("trueopt");
+ return $"arg1: {arg1} opt: {opt} b: {boolOpt} t: {trueOpt}";
}
}
\ No newline at end of file
diff --git a/tests/Flandre.Core.Tests/UtilsTests/CommandUtilsTests.cs b/tests/Flandre.Core.Tests/UtilsTests/CommandUtilsTests.cs
index c047e2b..c70511e 100644
--- a/tests/Flandre.Core.Tests/UtilsTests/CommandUtilsTests.cs
+++ b/tests/Flandre.Core.Tests/UtilsTests/CommandUtilsTests.cs
@@ -6,8 +6,8 @@ public class CommandUtilsTests
{
[Theory]
[InlineData("", true, "double", default(double))]
- [InlineData("", true, "string", "")] // string arg type defaults to empty, not null
- [InlineData("[gamma]", false, "string", "")]
+ [InlineData("", true, "string", "1234")] // for option, allow required arg have default value
+ [InlineData("[gamma]", false, "string", "")] // string arg type defaults to empty, not null
[InlineData("[]", false, "string", "")]
[InlineData(" [epsilon: bool = true] ", false, "bool", true)]
[InlineData("[f: bool = 12345678] ", false, "bool", false)] // wrong default value type
diff --git a/tests/Flandre.Core.Tests/UtilsTests/TextUtilsTests.cs b/tests/Flandre.Core.Tests/UtilsTests/TextUtilsTests.cs
index 544bee3..b8b4055 100644
--- a/tests/Flandre.Core.Tests/UtilsTests/TextUtilsTests.cs
+++ b/tests/Flandre.Core.Tests/UtilsTests/TextUtilsTests.cs
@@ -11,4 +11,12 @@ public void TestRemoveString(string source, string result, string removal)
{
Assert.Equal(result, source.RemoveString(removal));
}
+
+ [Theory]
+ [InlineData("string", "ring", "st")]
+ [InlineData("string", "string", "")]
+ public void TestTrimStart(string source, string result, string trim)
+ {
+ Assert.Equal(result, source.TrimStart(trim));
+ }
}
\ No newline at end of file