From 9f7abd230fd16968a55763d066dd1ba00478fc0d Mon Sep 17 00:00:00 2001 From: Andy Zhang <37402126+AnzhiZhang@users.noreply.github.com> Date: Thu, 29 Dec 2022 03:22:01 +0000 Subject: [PATCH] =?UTF-8?q?feat(dict=5Fcommand=5Fregistration):=20?= =?UTF-8?q?=F0=9F=8E=89=20new=20plugin=20`dict=5Fcommand=5Fregistration`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dict_command_registration/__init__.py | 55 ++++ .../dict_command_registration/exceptions.py | 8 + .../dict_command_registration/node.py | 134 ++++++++++ .../dict_command_registration/node_type.py | 15 ++ .../mcdreforged.plugin.json | 14 + dict_command_registration/readme.md | 250 ++++++++++++++++++ plugin_list.json | 1 + 7 files changed, 477 insertions(+) create mode 100644 dict_command_registration/dict_command_registration/__init__.py create mode 100644 dict_command_registration/dict_command_registration/exceptions.py create mode 100644 dict_command_registration/dict_command_registration/node.py create mode 100644 dict_command_registration/dict_command_registration/node_type.py create mode 100644 dict_command_registration/mcdreforged.plugin.json create mode 100644 dict_command_registration/readme.md diff --git a/dict_command_registration/dict_command_registration/__init__.py b/dict_command_registration/dict_command_registration/__init__.py new file mode 100644 index 0000000..28763e7 --- /dev/null +++ b/dict_command_registration/dict_command_registration/__init__.py @@ -0,0 +1,55 @@ +from typing import Any, Dict + +from mcdreforged.api.types import PluginServerInterface + +from .exceptions import MissingRequiredAttribute +from .node_type import NodeType +from .node import Node + +__all__ = [ + "MissingRequiredAttribute", + "NodeType", + "Node", + "register", +] + + +def register( + server: PluginServerInterface, + command: Dict[str, Any], + help_message: str = None, + help_message_permission: int = 0 +) -> None: + """ + Register a command. + See also: https://github.com/AnzhiZhang/MCDReforgedPlugins/blob/master/dict_command_registration/readme.md#register + :param PluginServerInterface server: the PluginServerInterface instance of + your plugin, to ensure that this command is registered by your plugin. + :param dict command: Command, please find more information in the document. + :param str help_message: Provide a string value if you want register + help message for this command. + :param int help_message_permission: The minimum permission level to see + this help message. See also in MCDReforged document. + :return: None. + """ + # parse dict + root_node = Node(command) + root_node.to_mcdr_node().print_tree() + + # register command + server.register_command(root_node.to_mcdr_node()) + + # register help message + if help_message is not None: + # get literal + if isinstance(root_node.literal, str): + literal = root_node.literal + else: + literal = root_node.literal[0] + + # register + server.register_help_message( + literal, + help_message, + help_message_permission + ) diff --git a/dict_command_registration/dict_command_registration/exceptions.py b/dict_command_registration/dict_command_registration/exceptions.py new file mode 100644 index 0000000..8663b14 --- /dev/null +++ b/dict_command_registration/dict_command_registration/exceptions.py @@ -0,0 +1,8 @@ +__all__ = [ + "MissingRequiredAttribute", +] + + +class MissingRequiredAttribute(Exception): + def __init__(self, name: str): + super().__init__(f"Missing required attribute \"{name}\"") diff --git a/dict_command_registration/dict_command_registration/node.py b/dict_command_registration/dict_command_registration/node.py new file mode 100644 index 0000000..da32835 --- /dev/null +++ b/dict_command_registration/dict_command_registration/node.py @@ -0,0 +1,134 @@ +from enum import Enum +from typing import Any, Callable, Dict, Iterable, List, Type, Union + +from mcdreforged.api.command import * + +from .exceptions import MissingRequiredAttribute +from .node_type import NodeType + + +class Node: + def __init__(self, data: Dict[str, Any]): + self.__data = data + + # name + self.__name: str = data.get("name") + if self.__name is None: + raise MissingRequiredAttribute("name") + + # node + self.__node: Union[Literal, ArgumentNode] = data.get("node") + + # literal + self.__literal: Union[str, Iterable[str]] = data.get( + "literal", + self.__name + ) + + # type + self.__type: Union[NodeType, Type[ArgumentNode]] = data.get( + "type", + NodeType.LITERAL + ) + + # enumeration + self.__enumeration: Dict[str, Any] = data.get("enumeration", {}) + + # args + self.__args: List[Any] = data.get("args", []) + + # kwargs + self.__kwargs: Dict[str, Any] = data.get("kwargs", {}) + + # runs + self.__runs: Callable = data.get("runs") + + # requires + self.__requires: Callable = data.get("requires") + + # redirects + self.__redirects: AbstractNode = data.get("redirects") + + # suggests + self.__suggests: Callable = data.get("suggests") + + # on_error + self.__on_error: Dict[str, Any] = data.get("on_error") + + # on_child_error + self.__on_child_error: Dict[str, Any] = data.get("on_child_error") + + # children + self.__children: List[Node] = [] + for i in data.get("children", []): + self.__children.append(Node(i)) + + @property + def literal(self) -> Union[str, Iterable[str]]: + return self.__literal + + def to_mcdr_node(self) -> Union[Literal, ArgumentNode]: + if self.__node is None: + # instantiate node + mcdr_node: Union[Literal, ArgumentNode] + if type(self.__type) == NodeType: + if self.__type == NodeType.LITERAL: + mcdr_node = self.__type.value(self.__literal) + elif self.__type == NodeType.ENUMERATION: + mcdr_node = self.__type.value( + self.__name, + Enum(self.__name, self.__enumeration) + ) + else: + mcdr_node = self.__type.value(self.__name) + else: + mcdr_node = self.__type( + self.__name, + *self.__args, + **self.__kwargs + ) + + # runs + if self.__runs is not None: + mcdr_node.runs(self.__runs) + + # requires + if self.__requires is not None: + mcdr_node.requires(self.__requires) + + # redirects + if self.__redirects is not None: + mcdr_node.redirects(self.__redirects) + + # suggests + if self.__suggests is not None: + mcdr_node.suggests(self.__suggests) + + # on_error + if self.__on_error is not None: + mcdr_node.on_error( + self.__on_error.get("error_type", CommandError), + self.__on_error.get("handler", lambda *args: None), + handled=self.__on_error.get("handled", False), + ) + + # on_child_error + if self.__on_child_error is not None: + mcdr_node.on_child_error( + self.__on_child_error.get("error_type", CommandError), + self.__on_child_error.get("handler", lambda *args: None), + handled=self.__on_child_error.get("handled", False), + ) + + # runs + if self.__runs is not None: + mcdr_node.runs(self.__runs) + + # add children + for i in self.__children: + mcdr_node.then(i.to_mcdr_node()) + + # return + return mcdr_node + else: + return self.__node diff --git a/dict_command_registration/dict_command_registration/node_type.py b/dict_command_registration/dict_command_registration/node_type.py new file mode 100644 index 0000000..8c4239b --- /dev/null +++ b/dict_command_registration/dict_command_registration/node_type.py @@ -0,0 +1,15 @@ +from enum import Enum + +from mcdreforged.api.command import * + + +class NodeType(Enum): + LITERAL = Literal + NUMBER = Number + INTEGER = Integer + FLOAT = Float + TEXT = Text + QUOTABLE_TEXT = QuotableText + GREEDY_TEXT = GreedyText + BOOLEAN = Boolean + ENUMERATION = Enumeration diff --git a/dict_command_registration/mcdreforged.plugin.json b/dict_command_registration/mcdreforged.plugin.json new file mode 100644 index 0000000..5d4b16d --- /dev/null +++ b/dict_command_registration/mcdreforged.plugin.json @@ -0,0 +1,14 @@ +{ + "id": "dict_command_registration", + "version": "1.0.0", + "name": "Dict Command Registration", + "description": { + "en_us": "Register your command by a python dict", + "zh_cn": "用 Python 字典注册您的指令" + }, + "author": "Andy Zhang", + "link": "https://github.com/AnzhiZhang/MCDReforgedPlugins/tree/master/dict_command_registration", + "dependencies": { + "mcdreforged": "^2.5.0" + } +} diff --git a/dict_command_registration/readme.md b/dict_command_registration/readme.md new file mode 100644 index 0000000..93706c7 --- /dev/null +++ b/dict_command_registration/readme.md @@ -0,0 +1,250 @@ +# Dict Command Registration + +> Register your command with a python dict. + +MCDReforged implements a command system like [brigadier](https://github.com/Mojang/brigadier), but it is too difficult to use and not intuitive enough. When the tree becomes large, maintainability and readability become extremely poor. Then you have to split it into multiple child nodes, but when the child nodes become large you need to keep splitting them and end up in an infinite loop. + +This plugin provides an API that allows you to register MCDR command trees with python dict, which is also a tree structure - a more intuitive structure, isn't it? It takes the python dict you provide, generates the MCDR Command Node, then register it. You do not have to bother with the huge code tree, just maintain your dict tree. + +Incidentally, it can register the [help message](https://mcdreforged.readthedocs.io/en/latest/code_references/PluginServerInterface.html#mcdreforged.plugin.server_interface.PluginServerInterface.register_help_message) for you. + +## Quick Start + +Let's register this example command in MCDR doc: + +```text +Literal('!!email'). \ +then(Literal('list')). \ +then(Literal('remove'). \ + then(Integer('email_id')) +). \ +then(Literal('send'). \ + then(Text('player'). \ + then(GreedyText('message')) + ) +) +``` + +Write the command dict and call register method. + +```python +from dict_command_registration import NodeType, register + +command = { + "name": "!!email", + "children": [ + { + "name": "list" + }, + { + "name": "remove", + "children": [ + { + "name": "email_id", + "type": NodeType.INTEGER + } + ] + }, + { + "name": "send", + "children": [ + { + "name": "player", + "type": NodeType.TEXT, + "children": [ + { + "name": "email_id", + "type": NodeType.GREEDY_TEXT + } + ] + } + ] + } + ] +} + +def on_load(server, prev_module): + register(server, command) +``` + +All done! + +If you want register help message together: + +```python +register(server, command, "Email command") +``` + +## Concepts + +### Node + +In this plugin, `Node` means a dict which contains data of a MCDR +command node. + +See also: [Node](#node-dict) + +## API Reference + +### Exceptions + +#### MissingRequiredAttribute + +Raise when missing required attribute in [Node](#node-dict). + +### NodeType + +MCDR Origin Command Nodes. + +| Key | Class | +| - | - | +| LITERAL | [Literal](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.Literal) | +| NUMBER | [Number](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.Number) | +| INTEGER | [Integer](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.Integer) | +| FLOAT | [Float](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.Float) | +| TEXT | [Text](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.Text) | +| QUOTABLE_TEXT | [QuotableText](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.QuotableText) | +| GREEDY_TEXT | [GreedyText](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.GreedyText) | +| BOOLEAN | [Boolean](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.Boolean) | +| ENUMERATION | [Enumeration](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.Enumeration) | + +### Node (class) + +Parse a [Node (dict)](#node-dict), and can cast to MCDR node. + +#### __init__(data: Dict[str, Any]) + +Accept a dict. + +#### literal: Union[str, Iterable[str]] + +Get literal string or Iterable. + +#### to_mcdr_node() -> Union[Literal, ArgumentNode] + +To MCDR Node. + +### Node (dict) + +#### name + +> Name of the node. + +- Type: `str` + +This value is required. + +#### node + +> MCDR node if you want use exist node. + +- Type: [Literal](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.Literal) or [ArgumentNode](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.ArgumentNode) + +#### literal + +> Text of a literal node. + +- Type: `str` or `Iterable[str]` +- Default: [name](#name) value + +You have to set this value if you want use multiple literals (Iterable). + +#### type + +> Type of this node. + +- Type: [NodeType](#nodetype) or [ArgumentNode](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.ArgumentNode) +- Default: [NodeType.LITERAL](#literal) + +#### enumeration + +> Value of [Enumeration](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.arguments.Enumeration) node. + +- Type: `Dict[str, Any]` +- Default: `[]` + +#### args + +> Args to create Node if using customize node. + +- Type: `List[Any]` +- Default: `[]` + +#### kwargs + +> Kwargs to create Node if using customize node. + +- Type: `Dict[str, Any]` +- Default: `{}` + +#### runs + +> Set the callback function of this node. + +- Type: `Callable` + +See also: [AbstractNode.runs()](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.AbstractNode.runs). + +#### requires + +> Set the requirement tester callback of the node. + +- Type: `Callable` + +See also: [AbstractNode.requires()](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.AbstractNode.requires). + +#### redirects + +> Redirect all further child nodes command parsing to another given node. + +- Type: [AbstractNode](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.AbstractNode) + +See also: [AbstractNode.redirects()](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.AbstractNode.redirects). + +#### suggests + +> Set the provider for command suggestions of this node. + +- Type: `Callable` + +See also: [AbstractNode.suggests()](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.AbstractNode.suggests). + +#### on_error + +> When a command error occurs, the given will invoke the given handler to handle with the error. + +- Type: `Dict[str, Any]` + +See also: [AbstractNode.on_error()](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.AbstractNode.on_error). + +Accept three keys in the dict, which are three arguments listed in the doc. + +#### on_child_error + +> Similar to `on_error()`, but it gets triggered only when the node receives a command error from one of the node’s direct or indirect child. + +- Type: `Dict[str, Any]` + +See also: [AbstractNode.on_child_error()](https://mcdreforged.readthedocs.io/en/latest/code_references/command.html#mcdreforged.command.builder.nodes.basic.AbstractNode.on_child_error). + +Accept three keys in the dict, which are three arguments listed in the doc. + +#### children + +> Children of this node. + +- Type: `List[Dict[str, Any]]` +- Default: `[]` + +You can put node in the array to add a child node. + +### register + +Method to register command. + +Params: + +- PluginServerInterface server: the PluginServerInterface instance of your plugin, to ensure that this command is registered by your plugin. +- dict command: Command, please find more information in the document. +- str help_message: Provide a string value if you want register +- int help_message_permission: The minimum permission level to see this help message. See also in MCDReforged document. diff --git a/plugin_list.json b/plugin_list.json index d8316bd..6c7d69f 100644 --- a/plugin_list.json +++ b/plugin_list.json @@ -3,6 +3,7 @@ "bingo", "bot", "database_api", + "dict_command_registration", "gamemode", "info", "lowercase_mcdr_command",