Skip to content
This repository has been archived by the owner on Mar 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #8 from FlandreDevs/refactor-command-parser
Browse files Browse the repository at this point in the history
Refactor command parser
  • Loading branch information
bsdayo authored Oct 7, 2022
2 parents b06065d + ca7d531 commit 6017878
Show file tree
Hide file tree
Showing 24 changed files with 355 additions and 252 deletions.
12 changes: 8 additions & 4 deletions README.NuGet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

### 指令系统

Expand Down Expand Up @@ -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` 等等,在解析过程中会自动进行类型检查和转换。

举个例子:

Expand All @@ -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`

### 灵活的表现形式

Expand Down
2 changes: 1 addition & 1 deletion src/Flandre.Core/Attributes/CommandAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public CommandAttribute(string pattern)

while (!parser.IsEnd())
{
var bracket = parser.Current();
var bracket = parser.Current;
if (!"<[".Contains(bracket))
{
parser.Read(' ');
Expand Down
26 changes: 19 additions & 7 deletions src/Flandre.Core/Attributes/OptionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class OptionAttribute : Attribute
/// </summary>
public string Name { get; }

/// <summary>
/// 短名称
/// </summary>
public char? ShortName { get; }

/// <summary>
/// 选项别名
/// </summary>
Expand Down Expand Up @@ -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;
}
}
24 changes: 0 additions & 24 deletions src/Flandre.Core/Common/ArgumentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,4 @@ public T Get<T>(string name)
{
return (T)ArgumentList.First(arg => arg.Key == name).Value;
}

/// <summary>
/// 根据索引获取参数
/// </summary>
/// <param name="index">参数索引</param>
/// <typeparam name="T">返回类型</typeparam>
/// <returns>参数值,若索引越界或无法转换则返回 default(T)</returns>
public T? GetOrDefault<T>(int index)
{
var result = ArgumentList.ElementAtOrDefault(index).Value;
return result is T casted ? casted : default;
}

/// <summary>
/// 根据名称获取参数
/// </summary>
/// <param name="name">参数名称</param>
/// <typeparam name="T">返回类型</typeparam>
/// <returns>参数值,若未找到或无法转换则返回 default(T)</returns>
public T? GetOrDefault<T>(string name)
{
var result = ArgumentList.FirstOrDefault(arg => arg.Key == name).Value;
return result is T casted ? casted : default;
}
}
147 changes: 146 additions & 1 deletion src/Flandre.Core/Common/Command.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Reflection;
using Flandre.Core.Attributes;
using Flandre.Core.Messaging;
using Flandre.Core.Utils;

namespace Flandre.Core.Common;

Expand All @@ -23,10 +25,153 @@ public class Command
/// </summary>
public List<OptionAttribute> Options { get; }

internal Command(CommandAttribute info, MethodInfo innerMethod, List<OptionAttribute> options)
private readonly Logger _pluginLogger;

internal Command(CommandAttribute info, MethodInfo innerMethod, List<OptionAttribute> 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<string>();

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<string, object>(param.Name, parser.ReadQuoted()));
}
else
{
if (CommandUtils.TryParseType(parser.Read(' '),
param.Type, out var result, false))
args.Arguments.ArgumentList.Add(new KeyValuePair<string, object>(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<string, object>(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<MessageContent>)?.Result ?? null;

return content;
}
catch (TargetInvocationException te)
{
_pluginLogger.Error(te.InnerException ?? te);
}
catch (Exception e)
{
_pluginLogger.Error(e);
}

return null;
}
}
7 changes: 2 additions & 5 deletions src/Flandre.Core/Common/OptionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,14 @@ public class OptionManager : IEnumerable<KeyValuePair<string, object>>
/// <param name="key">选项名称</param>
/// <typeparam name="T">返回类型</typeparam>
/// <returns>若未提供该选项,或类型错误则返回类型默认值</returns>
public T? GetOrDefault<T>(string key)
public T Get<T>(string key)
{
var value = OptionsDict.GetValueOrDefault(key);
return value is not null ? (T)value : default;
return (T)OptionsDict[key];
}

/// <summary>
/// 获取 Enumerator
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return OptionsDict.GetEnumerator();
Expand Down
18 changes: 3 additions & 15 deletions src/Flandre.Core/Common/ParsedArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,12 @@ public T GetArgument<T>(string name)
}

/// <summary>
/// 根据索引获取参数
/// </summary>
/// <param name="index">参数索引</param>
/// <typeparam name="T">返回类型</typeparam>
/// <returns>参数值,若索引越界或无法转换则返回 default(T)</returns>
public T? GetArgumentOrDefault<T>(int index)
{
return Arguments.GetOrDefault<T>(index);
}

/// <summary>
/// 根据名称获取参数
/// 根据名称获取选项,值将被强制转换为 T 类型
/// </summary>
/// <param name="name">参数名称</param>
/// <typeparam name="T">返回类型</typeparam>
/// <returns>参数值,若未找到或无法转换则返回 default(T)</returns>
public T? GetArgumentOrDefault<T>(string name)
public T GetOption<T>(string name)
{
return Arguments.GetOrDefault<T>(name);
return Options.Get<T>(name);
}
}
Loading

0 comments on commit 6017878

Please sign in to comment.