gpt4 book ai didi

c# - 如何解析和执行命令行样式字符串?

转载 作者:行者123 更新时间:2023-12-02 01:31:34 25 4
gpt4 key购买 nike

最后,我确实有一个具体的问题,但是我想提供足够的背景和上下文,以便您尽可能地位于同一页面上,并且可以理解我的目标。

背景

我正在使用ASP.NET MVC 3构建一个控制台样式的应用程序。如果您需要去那里并了解它的运行方式,它位于http://www.u413.com。这个概念本身很简单:从客户端接收命令字符串,检查提供的命令是否存在,并且命令所提供的参数是否有效,执行命令,然后返回一组结果。

内部工作

通过这个应用程序,我决定要有所创造力。终端样式应用程序最明显的解决方案是构建世界上最大的IF语句。通过IF语句运行每个命令,然后从内部调用适当的函数。我不喜欢这个主意。在较旧版本的应用程序中,这就是它的操作方式,而且非常混乱。向应用程序添加功能非常困难。

经过三思而后行,我决定构建一个称为命令模块的自定义对象。想法是为每个请求构建此命令模块。模块对象将包含所有可用命令作为方法,然后站点将使用反射来检查用户提供的命令是否与方法名称匹配。命令模块对象位于名为ICommandModule的接口后面,如下所示。

namespace U413.Business.Interfaces
{
/// <summary>
/// All command modules must ultimately inherit from ICommandModule.
/// </summary>
public interface ICommandModule
{
/// <summary>
/// The method that will locate and execute a given command and pass in all relevant arguments.
/// </summary>
/// <param name="command">The command to locate and execute.</param>
/// <param name="args">A list of relevant arguments.</param>
/// <param name="commandContext">The current command context.</param>
/// <param name="controller">The current controller.</param>
/// <returns>A result object to be passed back tot he client.</returns>
object InvokeCommand(string command, List<string> args, CommandContext commandContext, Controller controller);
}
}


InvokeCommand()方法是我的MVC控制器立即意识到的命令模块上的唯一方法。然后,此方法的职责是使用反射并查看其实例并查找所有可用的命令方法。

我使用Ninject进行依赖项注入。我的MVC控制器对 ICommandModule具有构造函数依赖性。我构建了一个自定义的Ninject提供程序,用于在解决 ICommandModule依赖项时构建此命令模块。 Ninject可以构建四种命令模块:


VisitorCommandModule
UserCommandModule
ModeratorCommandModule
AdministratorCommandModule


还有一个 BaseCommandModule类,所有其他模块类都继承自该类。很快,继承关系如下:


BaseCommandModule : ICommandModule
VisitorCommandModule : BaseCommandModule
UserCommandModule : BaseCommandModule
ModeratorCommandModule : UserCommandModule
AdministratorCommandModule : ModeratorCommandModule


希望您现在能看到它是如何构造的。根据用户的成员资格状态(未登录,常规用户,主持人等),Ninject将为适当的命令模块提供仅用户应有权访问的命令方法。

所有这些都很好。当我解析命令字符串并弄清楚如何在命令模块对象上构造命令方法时,我的困境就出现了。

问题

命令字符串应如何解析和执行?

当前解决方案

目前,我在MVC控制器中分解了命令字符串(用户传递的包含命令和所有参数的字符串)。然后,在注入的 InvokeCommand()上调用 ICommandModule方法,并传入 string命令和 List<string> args。

假设我有以下命令:

TOPIC <id> [page #] [reply “reply”]


该行定义了TOPIC命令,该命令接受必需的ID号,可选的页码以及带有回复值的可选的Reply命令。

我目前正在实现这样的命令方法(方法上方的属性用于帮助菜单信息。HELP命令使用反射来读取所有这些命令并显示组织的帮助菜单):

    /// <summary>
/// Shows a topic and all replies to that topic.
/// </summary>
/// <param name="args">A string list of user-supplied arguments.</param>
[CommandInfo("Displays a topic and its replies.")]
[CommandArgInfo(Name="ID", Description="Specify topic ID to display the topic and all associated replies.", RequiredArgument=true)]
[CommandArgInfo(Name="REPLY \"reply\"", Description="Subcommands can be used to navigate pages, reply to the topic, edit topic or a reply, or delete topic or a reply.", RequiredArgument=false)]
public void TOPIC(List<string> args)
{
if ((args.Count == 1) && (args[0].IsInt64()))
TOPIC_Execute(args); // View the topic.
else if ((args.Count == 2) && (args[0].IsInt64()))
if (args[1].ToLower() == "reply")
TOPIC_ReplyPrompt(args); // Prompt user to input reply content.
else
_result.DisplayArray.Add("Subcommand Not Found");
else if ((args.Count >= 3) && (args[0].IsInt64()))
if (args[1].ToLower() == "reply")
TOPIC_ReplyExecute(args); // Post user's reply to the topic.
else
_result.DisplayArray.Add("Subcommand Not Found");
else
_result.DisplayArray.Add("Subcommand Not Found");
}


我目前的实现方式非常混乱。我想避免使用巨型IF语句,但是我所做的就是为所有命令交换一个巨型IF语句,而为每个命令及其参数换来的吨巨型IF语句要少很多。这还不算一半。我针对这个问题简化了该命令。在实际的实现中,此命令可以提供更多的参数,而IF语句是我见过的最丑陋的东西。这是非常冗余的,根本不干(不要重复),因为我必须在三个不同的地方显示“找不到子命令”。

可以说,我需要一个比这更好的解决方案。

理想的实现

理想情况下,我希望构建类似于他的命令方法:

public void TOPIC(int Id, int? page)
{
// Display topic to user, at specific page number if supplied.
}

public void TOPIC(int Id, string reply)
{
if (reply == null)
{
// prompt user for reply text.
}
else
{
// Add reply to topic.
}
}


然后,我想这样做:


从客户端接收命令字符串。
将命令字符串直接传递到 InvokeCommand()上的 ICommandModule中。
InvokeCommand()执行一些魔术分析和反射,以选择带有正确参数的正确命令方法并调用该方法,仅传入必要的参数。


理想实现的困境

我不确定如何构建此逻辑。几天来我一直在挠头。我希望我有另一双眼睛可以帮助我解决这个问题(因此终于诉诸于SO问题的小说)。事情应该以什么顺序发生?

我应该拔出命令,找到具有该命令名称的所有方法,然后遍历所有可能的参数,然后遍历我的命令字符串的参数吗?我如何确定去哪儿和哪些参数成对出现。例如,如果我遍历命令字符串并找到 Reply "reply",当遇到 <ID>数字并将其提供给 Id参数时,如何将答复内容与reply变量配对?

我确定我现在让您感到困惑。让我用一些用户可能会传入的命令字符串示例来说明:

TOPIC 36 reply // Should prompt the user to enter reply text.
TOPIC 36 reply "Hey guys what's up?" // Should post a reply to the topic.
TOPIC 36 // Should display page 1 of the topic.
TOPIC 36 page 4 // Should display page 4 of the topic.


我怎么知道发送36到 Id参数?我怎么知道配对回复“嘿,大家好吗?”并通过“嗨,大家好吗?”作为方法中reply参数的值?

为了知道要调用的方法重载,我需要知道提供了多少个参数,以便可以将该数字与采用相同数量参数的command方法的重载进行匹配。问题是,`主题36回答“嗨,大家好吗?”实际上是两个论点,而不是三个论点,而“嗨...”是一个论点。

我不介意过分(或大量)膨胀 InvokeCommand()方法,只要这意味着在那里处理了所有复杂的解析和反射废话,并且我的命令方法可以保持简洁,易于编写。

我想我真的只是在这里寻找一些见识。有谁有创意解决这个问题?这确实是一个大问题,因为IF语句参数当前使为应用程序编写新命令变得非常复杂。这些命令是我想要变得非常简单的应用程序的一部分,以便可以轻松地对其进行扩展和更新。这是我的应用程序中实际TOPIC命令方法的外观:

    /// <summary>
/// Shows a topic and all replies to that topic.
/// </summary>
/// <param name="args">A string list of user-supplied arguments.</param>
[CommandInfo("Displays a topic and its replies.")]
[CommandArgInfo("ID", "Specify topic ID to display the topic and all associated replies.", true, 0)]
[CommandArgInfo("Page#/REPLY/EDIT/DELETE [Reply ID]", "Subcommands can be used to navigate pages, reply to the topic, edit topic or a reply, or delete topic or a reply.", false, 1)]
public void TOPIC(List<string> args)
{
if ((args.Count == 1) && (args[0].IsLong()))
TOPIC_Execute(args);
else if ((args.Count == 2) && (args[0].IsLong()))
if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply")
TOPIC_ReplyPrompt(args);
else if (args[1].ToLower() == "edit")
TOPIC_EditPrompt(args);
else if (args[1].ToLower() == "delete")
TOPIC_DeletePrompt(args);
else
TOPIC_Execute(args);
else if ((args.Count == 3) && (args[0].IsLong()))
if ((args[1].ToLower() == "edit") && (args[2].IsLong()))
TOPIC_EditReplyPrompt(args);
else if ((args[1].ToLower() == "delete") && (args[2].IsLong()))
TOPIC_DeleteReply(args);
else if (args[1].ToLower() == "edit")
TOPIC_EditExecute(args);
else if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply")
TOPIC_ReplyExecute(args);
else if (args[1].ToLower() == "delete")
TOPIC_DeleteExecute(args);
else
_result.DisplayArray.Add(DisplayObject.InvalidArguments);
else if ((args.Count >= 3) && (args[0].IsLong()))
if (args[1].ToLower() == "reply" || args[1].ToLower() == "modreply")
TOPIC_ReplyExecute(args);
else if ((args[1].ToLower() == "edit") && (args[2].IsLong()))
TOPIC_EditReplyExecute(args);
else if (args[1].ToLower() == "edit")
TOPIC_EditExecute(args);
else
_result.DisplayArray.Add(DisplayObject.InvalidArguments);
else
_result.DisplayArray.Add(DisplayObject.InvalidArguments);
}


那不是很可笑吗?每个命令都有这样的怪物,这是不可接受的。我只是想一想脑中的场景以及代码如何处理它。现在,如果我可以为命令方法的实现感到骄傲,我就为我的命令模块设置感到骄傲。

尽管我不想为应用程序提供整个模型(命令模块),但我绝对愿意提出建议。我最感兴趣的是与解析命令行字符串并将其参数映射到正确的方法重载有关的建议。我敢肯定,无论采用哪种解决方案,都需要进行大量的重新设计,因此即使我不一定使用您的建议,也不要害怕提出您认为有价值的任何建议,这可能会使我走上正确的道路。

编辑:延长一个较长的问题

我只是想快速澄清一下,命令到命令方法的映射并不是我真正担心的事情。我最关心的是如何解析和组织命令行字符串。当前, InvokeCommand()方法使用一些非常简单的C#反射来找到合适的方法:

    /// <summary>
/// Invokes the specified command method and passes it a list of user-supplied arguments.
/// </summary>
/// <param name="command">The name of the command to be executed.</param>
/// <param name="args">A string list of user-supplied arguments.</param>
/// <param name="commandContext">The current command context.</param>
/// <param name="controller">The current controller.</param>
/// <returns>The modified result object to be sent to the client.</returns>
public object InvokeCommand(string command, List<string> args, CommandContext commandContext, Controller controller)
{
_result.CurrentContext = commandContext;
_controller = controller;

MethodInfo commandModuleMethods = this.GetType().GetMethod(command.ToUpper());
if (commandModuleMethods != null)
{
commandModuleMethods.Invoke(this, new object[] { args });
return _result;
}
else
return null;
}


如您所见,我并不担心如何查找命令方法,因为它已经在起作用。我只是在思考解析命令字符串,组织参数,然后使用该信息通过反射选择正确的命令方法/重载的好方法。

赏金更新

我已经开始悬赏这个问题了。我正在寻找一种解析传入的命令字符串的好方法。我希望解析器识别几件事:


选项。确定命令字符串中的选项。
名称/值对。识别名称/值对(例如[page#] <-包括关键字“ page”和值“#”)
仅值。仅识别价值。


我希望通过第一个命令方法重载上的元数据来标识它们。这是我要编写的示例方法列表,其中装饰有一些解析器在进行反射时要使用的元数据。我将为您提供这些方法示例以及一些应映射到该方法的示例命令字符串。然后,我将由您自己决定,如此优秀的SO伙计们可以提出一个好的解析器解决方案。

// Metadata to be used by the HELP command when displaying HELP menu, and by the
// command string parser when deciding what types of arguments to look for in the
// string. I want to place these above the first overload of a command method.
// I don't want to do an attribute on each argument as some arguments get passed
// into multiple overloads, so instead the attribute just has a name property
// that is set to the name of the argument. Same name the user should type as well
// when supplying a name/value pair argument (e.g. Page 3).

[CommandInfo("Test command tests things.")]
[ArgInfo(
Name="ID",
Description="The ID of the topic.",
ArgType=ArgType.ValueOnly,
Optional=false
)]
[ArgInfo(
Name="PAGE",
Description="The page number of the topic.",
ArgType=ArgType.NameValuePair,
Optional=true
)]
[ArgInfo(
Name="REPLY",
Description="Context shortcut to execute a reply.",
ArgType=ArgType.NameValuePair,
Optional=true
)]
[ArgInfo(
Name="OPTIONS",
Description="One or more options.",
ArgType=ArgType.MultiOption,
Optional=true
PossibleValues=
{
{ "-S", "Sort by page" },
{ "-R", "Refresh page" },
{ "-F", "Follow topic." }
}
)]
[ArgInfo(
Name="SUBCOMMAND",
Description="One of several possible subcommands.",
ArgType=ArgType.SingleOption,
Optional=true
PossibleValues=
{
{ "NEXT", "Advance current page by one." },
{ "PREV", "Go back a page." },
{ "FIRST", "Go to first page." },
{ "LAST", "Go to last page." }
}
)]
public void TOPIC(int id)
{
// Example Command String: "TOPIC 13"
}

public void TOPIC(int id, int page)
{
// Example Command String: "TOPIC 13 page 2"
}

public void TOPIC(int id, string reply)
{
// Example Command String: TOPIC 13 reply "reply"

// Just a shortcut argument to another command.
// Executes actual reply command.
REPLY(id, reply, { "-T" });
}

public void TOPIC(int id, List<string> options)
{
// options collection should contain a list of supplied options

Example Command String: "TOPIC 13 -S",
"TOPIC 13 -S -R",
"TOPIC 13 -R -S -F",
etc...
}


解析器必须输入命令字符串,使用反射查找所有可能的命令方法重载,使用反射读取参数属性,以帮助确定如何将字符串分成适当的参数列表,然后调用适当的命令方法重载,传递适当的论点。

最佳答案

看一下Mono.Options。它目前是Mono框架的一部分,但可以下载并用作单个库。

您可以obtain it here,也可以grab the current version used in Mono as a single file

string data = null;
bool help = false;
int verbose = 0;
var p = new OptionSet () {
{ "file=", v => data = v },
{ "v|verbose", v => { ++verbose } },
{ "h|?|help", v => help = v != null },
};
List<string> extra = p.Parse (args);

关于c# - 如何解析和执行命令行样式字符串?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6235345/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com