mcdreforged.command.builder.tools 源代码

from abc import ABC
from typing import Dict, Callable, TYPE_CHECKING, Optional, List, TypeVar, Generic, Any, Type, overload

from typing_extensions import Self

from mcdreforged.command.builder.exception import CommandError
from mcdreforged.command.builder.nodes.basic import RUNS_CALLBACK, Literal, AbstractNode, ArgumentNode, REQUIRES_CALLBACK, FAIL_MSG_CALLBACK, SUGGESTS_CALLBACK, ERROR_HANDLER_CALLBACK
from mcdreforged.command.command_source import CommandSource
from mcdreforged.utils import tree_printer

if TYPE_CHECKING:
	from mcdreforged.plugin.server_interface import PluginServerInterface


[文档] class Requirements: """ A common callback function factory for command node requirement testing Example usage:: node.requires(Requirements.has_permission(1)) .. seealso:: Method :meth:`AbstractNode.requires() <mcdreforged.command.builder.nodes.basic.AbstractNode.requires>`, method :meth:`NodeDefinition.requires() <mcdreforged.command.builder.tools.NodeDefinition.requires>` .. versionadded:: v2.6.0 """
[文档] @classmethod def has_permission(cls, level: int) -> Callable[[CommandSource], bool]: """ Check if the command source has the given permission level :param level: The minimum accepted permission level """ def callback(source: CommandSource) -> bool: return source.has_permission(level) return callback
[文档] @classmethod def is_player(cls) -> Callable[[CommandSource], bool]: """ Check if the command source indicates a player """ def callback(source: CommandSource) -> bool: return source.is_player return callback
[文档] @classmethod def is_console(cls) -> Callable[[CommandSource], bool]: """ Check if the command source indicates the console """ def callback(source: CommandSource) -> bool: return source.is_console return callback
[文档] @classmethod def argument_exists(cls, arg_name: str) -> Callable[[CommandSource, dict], bool]: """ Check if the given argument has been assigned in current command context :param arg_name: The name of the argument to be checked """ def callback(_source: CommandSource, context: dict) -> bool: return arg_name in context return callback
NodeType = TypeVar('NodeType', bound=AbstractNode) ArgNodeType = TypeVar('ArgNodeType', bound=ArgumentNode) CommandCallbackFunc = TypeVar('CommandCallbackFunc', bound=RUNS_CALLBACK) CommandCallbackDecorator = Callable[[CommandCallbackFunc], CommandCallbackFunc]
[文档] class NodeDefinition(Generic[NodeType], ABC): """ A node definition class holding extra customization information """
[文档] def post_process(self, post_processor: Callable[[NodeType], Any]) -> Self: """ Add a post-process function to the current node definition During method :meth:`SimpleCommandBuilder.build`, after a node is constructed, all the post-process functions will be applied to the node object """ raise NotImplementedError()
[文档] def requires(self, requirement: REQUIRES_CALLBACK, failure_message_getter: Optional[FAIL_MSG_CALLBACK] = None) -> Self: """ See :meth:`AbstractNode.requires() <mcdreforged.command.builder.nodes.basic.AbstractNode.requires>` """ raise NotImplementedError()
[文档] def suggests(self, suggestion: SUGGESTS_CALLBACK) -> Self: """ See :meth:`AbstractNode.suggests() <mcdreforged.command.builder.nodes.basic.AbstractNode.suggests>` """ raise NotImplementedError()
[文档] def on_error(self, error_type: Type[CommandError], handler: ERROR_HANDLER_CALLBACK, *, handled: bool = False) -> Self: """ See :meth:`AbstractNode.on_error() <mcdreforged.command.builder.nodes.basic.AbstractNode.on_error>` """ raise NotImplementedError()
[文档] def on_child_error(self, error_type: Type[CommandError], handler: ERROR_HANDLER_CALLBACK, *, handled: bool = False) -> Self: """ See :meth:`AbstractNode.on_child_error() <mcdreforged.command.builder.nodes.basic.AbstractNode.on_child_error>` """ raise NotImplementedError()
class _NodeDefinitionImpl(NodeDefinition): def __init__(self, node_factory: Callable[[str], NodeType]): self.__node_factory = node_factory self.__node_processors: List[Callable[[NodeType], Any]] = [] def create_node(self, node_name: str) -> NodeType: node = self.__node_factory(node_name) for processor in self.__node_processors: processor(node) return node def post_process(self, post_processor: Callable[[NodeType], Any]) -> Self: self.__node_processors.append(post_processor) return self def requires(self, requirement: REQUIRES_CALLBACK, failure_message_getter: Optional[FAIL_MSG_CALLBACK] = None) -> Self: return self.post_process(lambda n: n.requires(requirement, failure_message_getter)) def suggests(self, suggestion: SUGGESTS_CALLBACK) -> Self: return self.post_process(lambda n: n.suggests(suggestion)) def on_error(self, error_type: Type[CommandError], handler: ERROR_HANDLER_CALLBACK, *, handled: bool = False) -> Self: return self.post_process(lambda n: n.on_error(error_type, handler, handled=handled)) def on_child_error(self, error_type: Type[CommandError], handler: ERROR_HANDLER_CALLBACK, *, handled: bool = False) -> Self: return self.post_process(lambda n: n.on_child_error(error_type, handler, handled=handled))
[文档] class SimpleCommandBuilder: """ A tree-free command builder for easier command building. Declare & Define, that's all you need .. versionadded:: v2.6.0 """
[文档] class Error(Exception): """ Custom exception to be thrown in :class:`SimpleCommandBuilder` """ pass
__DEFAULT_LITERAL_DEFINITION = _NodeDefinitionImpl(Literal) def __init__(self): self.__commands: Dict[str, RUNS_CALLBACK] = {} self.__literals: Dict[str, _NodeDefinitionImpl[Literal]] = {} self.__arguments: Dict[str, _NodeDefinitionImpl[ArgumentNode]] = {} self.__build_cache: Optional[List[AbstractNode]] = None @classmethod def __is_arg(cls, node_name: str) -> bool: return len(node_name) > 0 and node_name[0] == '<' and node_name[-1] == '>' @classmethod def __make_arg(cls, arg_name: str) -> str: return '<{}>'.format(arg_name) @classmethod def __strip_arg(cls, node_name: str) -> str: return node_name[1:-1] if cls.__is_arg(node_name) else node_name def __locate_or_create_child(self, parent: AbstractNode, node_name: str) -> AbstractNode: child_map: Dict[str, AbstractNode] = {} for child in parent.get_children(): if isinstance(child, Literal): for literal in child.literals: child_map[literal] = child elif isinstance(child, ArgumentNode): child_map[self.__make_arg(child.get_name())] = child else: # it should never go here raise TypeError('Unexpected node type {}'.format(child.__class__)) child = child_map.get(node_name) if child is None: child_factory: _NodeDefinitionImpl[AbstractNode] if self.__is_arg(node_name): child_factory = self.__arguments.get(node_name) if child_factory is None: raise self.Error('Undefined arg {}'.format(node_name)) else: child_factory = self.__literals.get(node_name, self.__DEFAULT_LITERAL_DEFINITION) child = child_factory.create_node(self.__strip_arg(node_name)) parent.then(child) return child # -------------- # Interfaces # -------------- @overload def command(self, command: str) -> CommandCallbackDecorator: ... @overload def command(self, command: str, callback: RUNS_CALLBACK) -> None: ...
[文档] def command(self, command: str, callback: Optional[RUNS_CALLBACK] = None): """ Define a command and its callback A command path string is made up of several elements separated by spaces. These elements are the names of corresponding command node. They describe a path from the root node to the target node in the command tree If a node has a name surrounding with ``"<"`` and ``">"``, it will be considered as an argument node, e.g. ``"<my_arg>"``. Otherwise it will be considered as a literal node, e.g. ``"my_literal"`` You need to give definitions of argument nodes with the :meth:`arg` method. You can also define your custom literal nodes with the :meth:`literal` method Examples:: builder.command('!!email list', list_email) builder.command('!!email remove <email_id>', remove_email) builder.command('!!email send <player> <message>', send_email) Alternative, you can use this method as a decorator to decorate the callback function by passing the first argument:: # This syntactic sugar does the same thing as `builder.command('!!email list', list_email)` @builder.command('!!email list') def list_email(source: CommandSource, context: CommandContext): pass # You can even chain the decorator. All given commands will use function `foo_bar()` as the callback @builder.command('!!email foo') @builder.command('!!email bar') def foo_bar(source: CommandSource, context: CommandContext): pass :param command: A command path string, e.g. ``"!!calc add <value_a> <value_b>"`` :param callback: The callback function of this command, which will be passed to :meth:`AbstractNode.then<mcdreforged.command.builder.nodes.basic.AbstractNode.then>` .. versionadded:: v2.10.0 It can now be used as a decorator for callback functions """ if callback is None: # decorator mode def decorator(func): self.command(command, func) return func return decorator self.__commands[command] = callback self.clean_cache()
[文档] def arg(self, arg_name: str, node_factory: Callable[[str], ArgNodeType]) -> NodeDefinition[ArgNodeType]: """ Define an argument node for an argument name. All argument names appeared in :meth:`command` must be defined Notes that almost all MCDR builtin argument node classes can be constructed with 1 argument name parameter (e.g. :class:`~mcdreforged.command.builder.nodes.arguments.Text`, :class:`~mcdreforged.command.builder.nodes.arguments.Number`), so you can just use the name of the argument class here Examples:: builder.arg('my_arg', QuotableText) builder.arg('my_arg', lambda name: Integer(name).at_min(0)) :param arg_name: The name of the argument node. It can be quoted with ``"<>"`` if you want. Examples: ``"my_arg"``, ``"<my_arg>"`` :param node_factory: An argument node constructor, that accepts the argument name as the only parameter and return an :class:`~mcdreforged.command.builder.nodes.basic.ArgumentNode` object :return: A :class:`NodeDefinition` object. With that you can further customize this node definition """ if not self.__is_arg(arg_name): arg_name = self.__make_arg(arg_name) definition = _NodeDefinitionImpl(node_factory) self.__arguments[arg_name] = definition self.clean_cache() return definition
[文档] def literal(self, literal_name: str, node_factory: Optional[Callable[[str], Literal]] = None) -> NodeDefinition[Literal]: """ Define a literal node for a literal name. It's useful when you want to have some custom literal nodes. If you just want a regular literal node, you don't need to invoke this method, since the builder will use the default :class:`~mcdreforged.command.builder.nodes.basic.Literal` constructor for node construction :param literal_name: The name of the literal node :param node_factory: A literal node constructor, that accepts the literal name as the only parameter and return a :class:`~mcdreforged.command.builder.nodes.basic.Literal` object. Optional :return: A :class:`NodeDefinitionImpl` object. With that you can further customize this node definition """ if node_factory is None: node_factory = Literal definition = _NodeDefinitionImpl(node_factory) self.__literals[literal_name] = definition self.clean_cache() return definition
# -------------- # Outputs # --------------
[文档] def build(self, *, use_cache: bool = True) -> List[AbstractNode]: """ Build the command trees Nodes with same name will be reused. e.g. if you define 3 commands with path ``"!!foo"``, ``"!!foo bar"`` and "``!!foo baz"``, the root ``"!!foo"`` node will be reused, and there will be only 1 ``"!!foo"`` node eventually :keyword use_cache: If set to false, do not use cache and always build new command trees :return: A list of the built command tree root nodes. The result is cached until you instruct the builder again. You can use :meth:`clean_cache` to explicitly clean the build cache :raise SimpleCommandBuilder.Error: if there are undefined argument nodes .. versionadded:: v2.9.0 Added ``use_cache`` keyword argument """ if not use_cache or self.__build_cache is None: root = Literal('#TEMP') for command, callback in self.__commands.items(): node = root for segment in command.split(' '): if len(segment) > 0: node = self.__locate_or_create_child(node, segment) node.runs(callback) self.__build_cache = root.get_children() return self.__build_cache
[文档] def clean_cache(self): """ Clean the build cache See method :meth:`build` """ self.__build_cache = None
[文档] def register(self, server: 'PluginServerInterface'): """ A helper method for lazyman, to build with method :meth:`build` and register built commands to the MCDR server :param server: The :class:`~mcdreforged.plugin.server_interface.PluginServerInterface` object of your plugin :raise SimpleCommandBuilder.Error: if build fails, or there are rooted non-literal nodes in the build result """ for node in self.build(): if isinstance(node, Literal): server.register_command(node) else: raise self.Error('Not-literal root node is not supported'.format(node))
[文档] def add_children_for(self, parent_node: AbstractNode): """ A helper method for lazyman, to build command trees with method :meth:`build` and attach all built tree nodes as the children of the given node via :meth:`~mcdreforged.command.builder.nodes.basic.AbstractNode.then` :param parent_node: The command node that will become the parent of the built children nodes :raise SimpleCommandBuilder.Error: if build fails .. versionadded:: v2.8.0 """ for node in self.build(): parent_node.then(node)
[文档] def print_tree(self, line_writer: tree_printer.LineWriter): """ A helper method for lazyman, to build with method :meth:`build` and print the built command trees Example:: builder.print_tree(server.logger.info) :param line_writer: A printer function that accepts a str :raise SimpleCommandBuilder.Error: if build fails """ for node in self.build(): node.print_tree(line_writer)