mcemu 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. mcemu-0.1.0/LICENSE +21 -0
  2. mcemu-0.1.0/PKG-INFO +42 -0
  3. mcemu-0.1.0/README.md +2 -0
  4. mcemu-0.1.0/mcemu/__init__.py +4 -0
  5. mcemu-0.1.0/mcemu/command_tree/arguments.py +176 -0
  6. mcemu-0.1.0/mcemu/command_tree/builder.py +10 -0
  7. mcemu-0.1.0/mcemu/command_tree/dispatcher.py +135 -0
  8. mcemu-0.1.0/mcemu/command_tree/execute_modifiers.py +77 -0
  9. mcemu-0.1.0/mcemu/command_tree/execute_node.py +15 -0
  10. mcemu-0.1.0/mcemu/command_tree/nodes.py +68 -0
  11. mcemu-0.1.0/mcemu/commands/__init__.py +13 -0
  12. mcemu-0.1.0/mcemu/commands/data.py +125 -0
  13. mcemu-0.1.0/mcemu/commands/execute.py +43 -0
  14. mcemu-0.1.0/mcemu/commands/function_cmds.py +34 -0
  15. mcemu-0.1.0/mcemu/commands/kill.py +18 -0
  16. mcemu-0.1.0/mcemu/commands/schedule.py +31 -0
  17. mcemu-0.1.0/mcemu/commands/scoreboard.py +109 -0
  18. mcemu-0.1.0/mcemu/commands/summon.py +36 -0
  19. mcemu-0.1.0/mcemu/commands/tag.py +27 -0
  20. mcemu-0.1.0/mcemu/commands/tick.py +33 -0
  21. mcemu-0.1.0/mcemu/commands/tp.py +52 -0
  22. mcemu-0.1.0/mcemu/context.py +67 -0
  23. mcemu-0.1.0/mcemu/emulator.py +98 -0
  24. mcemu-0.1.0/mcemu/entity.py +76 -0
  25. mcemu-0.1.0/mcemu/exceptions.py +3 -0
  26. mcemu-0.1.0/mcemu/tokenizer.py +85 -0
  27. mcemu-0.1.0/mcemu.egg-info/PKG-INFO +42 -0
  28. mcemu-0.1.0/mcemu.egg-info/SOURCES.txt +33 -0
  29. mcemu-0.1.0/mcemu.egg-info/dependency_links.txt +1 -0
  30. mcemu-0.1.0/mcemu.egg-info/requires.txt +5 -0
  31. mcemu-0.1.0/mcemu.egg-info/top_level.txt +1 -0
  32. mcemu-0.1.0/pyproject.toml +30 -0
  33. mcemu-0.1.0/setup.cfg +4 -0
  34. mcemu-0.1.0/tests/test_exceptions.py +7 -0
  35. mcemu-0.1.0/tests/test_tokenizer.py +46 -0
mcemu-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oğuzhan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
mcemu-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcemu
3
+ Version: 0.1.0
4
+ Summary: Minecraft command emulator library
5
+ Author: OguzhanUmutlu
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Oğuzhan
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/OguzhanUmutlu/mcemu
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: OS Independent
32
+ Requires-Python: >=3.8
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7.0; extra == "dev"
37
+ Requires-Dist: build>=0.10.0; extra == "dev"
38
+ Requires-Dist: twine>=4.0.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # mcemu
42
+ Minecraft command emulator library for PyPI
mcemu-0.1.0/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # mcemu
2
+ Minecraft command emulator library for PyPI
@@ -0,0 +1,4 @@
1
+ from .emulator import Emulator
2
+ from .entity import Entity, Player, World
3
+
4
+ __all__ = ["Emulator", "Entity", "Player", "World"]
@@ -0,0 +1,176 @@
1
+ class CommandSyntaxError(Exception):
2
+ pass
3
+
4
+
5
+ class ArgumentType:
6
+ def get_name(self) -> str:
7
+ return self.__class__.__name__
8
+
9
+ def parse(self, tokens, pos: int):
10
+ raise NotImplementedError()
11
+
12
+
13
+ class IntArgument(ArgumentType):
14
+ def parse(self, tokens, pos: int):
15
+ if pos >= len(tokens):
16
+ raise CommandSyntaxError("Expected integer")
17
+ t = tokens[pos]
18
+ try:
19
+ return int(t.value), pos + 1
20
+ except ValueError:
21
+ raise CommandSyntaxError(f"Expected integer, got '{t.value}'")
22
+
23
+
24
+ class FloatArgument(ArgumentType):
25
+ def parse(self, tokens, pos: int):
26
+ if pos >= len(tokens):
27
+ raise CommandSyntaxError("Expected float")
28
+ t = tokens[pos]
29
+ try:
30
+ return float(t.value), pos + 1
31
+ except ValueError:
32
+ raise CommandSyntaxError(f"Expected float, got '{t.value}'")
33
+
34
+
35
+ class StringArgument(ArgumentType):
36
+ def parse(self, tokens, pos: int):
37
+ if pos >= len(tokens):
38
+ raise CommandSyntaxError("Expected string")
39
+ t = tokens[pos]
40
+ return t.value, pos + 1
41
+
42
+
43
+ class GreedyStringArgument(ArgumentType):
44
+ def parse(self, tokens, pos: int):
45
+ if pos >= len(tokens):
46
+ raise CommandSyntaxError("Expected string")
47
+ val = ""
48
+ while pos < len(tokens):
49
+ val += tokens[pos].value + " "
50
+ pos += 1
51
+ return val.strip(), pos
52
+
53
+
54
+ class PathArgument(ArgumentType):
55
+ def parse(self, tokens, pos: int):
56
+ if pos >= len(tokens):
57
+ raise CommandSyntaxError("Expected path")
58
+
59
+ val = ""
60
+ if tokens[pos].type == "WORD":
61
+ val += tokens[pos].value
62
+ pos += 1
63
+
64
+ while pos < len(tokens):
65
+ t = tokens[pos]
66
+ if t.value in ("/", "-", "_", ":", ".", "\\"):
67
+ val += t.value
68
+ pos += 1
69
+ if pos < len(tokens) and tokens[pos].type == "WORD":
70
+ val += tokens[pos].value
71
+ pos += 1
72
+ else:
73
+ break
74
+
75
+ if not val:
76
+ raise CommandSyntaxError("Expected path")
77
+ return val, pos
78
+
79
+
80
+ class TimeArgument(ArgumentType):
81
+ def parse(self, tokens, pos: int):
82
+ if pos >= len(tokens):
83
+ raise CommandSyntaxError("Expected time")
84
+
85
+ t = tokens[pos]
86
+ val = t.value
87
+ pos += 1
88
+
89
+ multiplier = 1
90
+ if val.endswith("t"):
91
+ val = val[:-1]
92
+ elif val.endswith("s"):
93
+ val = val[:-1]
94
+ multiplier = 20
95
+ elif val.endswith("d"):
96
+ val = val[:-1]
97
+ multiplier = 24000
98
+
99
+ try:
100
+ return int(val) * multiplier, pos
101
+ except ValueError:
102
+ raise CommandSyntaxError(f"Expected time, got '{t.value}'")
103
+
104
+
105
+ class WordArgument(ArgumentType):
106
+ def parse(self, tokens, pos: int):
107
+ if pos >= len(tokens):
108
+ raise CommandSyntaxError("Expected word")
109
+
110
+ val = tokens[pos].value
111
+ pos += 1
112
+
113
+ while pos < len(tokens):
114
+ t = tokens[pos]
115
+ if t.value in (":", "."):
116
+ val += t.value
117
+ pos += 1
118
+ if pos < len(tokens) and tokens[pos].type == "WORD":
119
+ val += tokens[pos].value
120
+ pos += 1
121
+ else:
122
+ break
123
+ return val, pos
124
+
125
+
126
+ class ObjectiveArgument(WordArgument):
127
+ def get_name(self) -> str:
128
+ return "objective"
129
+
130
+
131
+ class TargetSelectorData:
132
+ def __init__(self, base: str, arguments: dict = None, is_pseudo: bool = False):
133
+ self.base = base
134
+ self.arguments = arguments
135
+ self.is_pseudo = is_pseudo
136
+
137
+
138
+ class PseudoSelectorArgument(ArgumentType):
139
+ def get_name(self) -> str:
140
+ return "pseudo_target"
141
+
142
+ def parse(self, tokens, pos: int):
143
+ if pos >= len(tokens):
144
+ raise CommandSyntaxError("Expected target selector or name")
145
+
146
+ t = tokens[pos]
147
+ if t.value == "@":
148
+ pos += 1
149
+ if pos >= len(tokens):
150
+ raise CommandSyntaxError("Incomplete target selector")
151
+ base = tokens[pos].value
152
+ pos += 1
153
+ args = {}
154
+ if pos < len(tokens) and tokens[pos].value == "[":
155
+ pos += 1
156
+ while pos < len(tokens) and tokens[pos].value != "]":
157
+ key_val, pos = WordArgument().parse(tokens, pos)
158
+ if pos >= len(tokens) or tokens[pos].value != "=":
159
+ raise CommandSyntaxError("Expected '=' in selector")
160
+ pos += 1
161
+ val, pos = WordArgument().parse(tokens, pos)
162
+ args[key_val] = val
163
+ if pos < len(tokens) and tokens[pos].value == ",":
164
+ pos += 1
165
+ if pos >= len(tokens) or tokens[pos].value != "]":
166
+ raise CommandSyntaxError("Expected ']'")
167
+ pos += 1
168
+ return TargetSelectorData(base, args, is_pseudo=False), pos
169
+ else:
170
+ val, pos = WordArgument().parse(tokens, pos)
171
+ return TargetSelectorData(val, None, is_pseudo=True), pos
172
+
173
+
174
+ class SelectorArgument(PseudoSelectorArgument):
175
+ def get_name(self) -> str:
176
+ return "target"
@@ -0,0 +1,10 @@
1
+ from .arguments import ArgumentType
2
+ from .nodes import LiteralNode, ArgumentNode
3
+
4
+
5
+ def literal(name: str) -> LiteralNode:
6
+ return LiteralNode(name)
7
+
8
+
9
+ def argument(name: str, arg_type: ArgumentType) -> ArgumentNode:
10
+ return ArgumentNode(name, arg_type)
@@ -0,0 +1,135 @@
1
+ from typing import List, Dict, Any, Tuple
2
+
3
+ from .arguments import CommandSyntaxError, TargetSelectorData
4
+ from .nodes import CommandNode, LiteralNode, ArgumentNode
5
+ from ..context import ExecutionContext, resolve_target_selector
6
+ from ..tokenizer import Token
7
+
8
+
9
+ class ParseContext:
10
+ def __init__(self, ctx: ExecutionContext):
11
+ self.ctx = ctx
12
+ self.arguments: Dict[str, Any] = {}
13
+ self.executable = None
14
+ self.modifiers: List[Tuple[Any, Dict[str, Any]]] = []
15
+
16
+ def clone(self):
17
+ new_ctx = ParseContext(self.ctx)
18
+ new_ctx.arguments = self.arguments.copy()
19
+ new_ctx.executable = self.executable
20
+ new_ctx.modifiers = list(self.modifiers)
21
+ return new_ctx
22
+
23
+
24
+ class CommandDispatcher:
25
+ def __init__(self):
26
+ self.root = CommandNode()
27
+
28
+ def register(self, node: CommandNode):
29
+ self.root.children[node.get_name()] = node
30
+
31
+ def get_valid_paths(self, node: CommandNode) -> List[str]:
32
+ return [child.get_usage() for child in node.children.values()]
33
+
34
+ def parse_node(self, node: CommandNode, tokens: List[Token], pos: int, parse_ctx: ParseContext) -> Tuple[
35
+ bool, int, ParseContext, CommandNode]:
36
+
37
+ if isinstance(node, (LiteralNode, ArgumentNode)):
38
+ try:
39
+ val, pos = node.parse(tokens, pos)
40
+ if isinstance(node, ArgumentNode):
41
+ parse_ctx.arguments[node.name] = val
42
+ except CommandSyntaxError:
43
+ return False, pos, parse_ctx, node
44
+
45
+ if node.executable:
46
+ parse_ctx.executable = node.executable
47
+
48
+ if pos >= len(tokens):
49
+ if parse_ctx.executable:
50
+ return True, pos, parse_ctx, node
51
+ return False, pos, parse_ctx, node
52
+
53
+ if node.redirect:
54
+ if node.modifier:
55
+ parse_ctx.modifiers.append((node.modifier, parse_ctx.arguments.copy()))
56
+ return self.parse_node(node.redirect, tokens, pos, parse_ctx)
57
+
58
+ best_fail_node = node
59
+ best_fail_pos = pos
60
+
61
+ for child in node.children.values():
62
+ child_ctx = parse_ctx.clone()
63
+ success, child_pos, final_ctx, last_node = self.parse_node(child, tokens, pos, child_ctx)
64
+ if success:
65
+ return True, child_pos, final_ctx, last_node
66
+
67
+ if child_pos > best_fail_pos:
68
+ best_fail_pos = child_pos
69
+ best_fail_node = last_node
70
+
71
+ return False, best_fail_pos, parse_ctx, best_fail_node
72
+
73
+ def dispatch(self, tokens: List[Token], ctx: ExecutionContext) -> int:
74
+ if not tokens:
75
+ return 0
76
+
77
+ parse_ctx = ParseContext(ctx)
78
+ best_fail_pos = 0
79
+ best_fail_node = self.root
80
+
81
+ for child in self.root.children.values():
82
+ child_ctx = parse_ctx.clone()
83
+ success, pos, final_ctx, last_node = self.parse_node(child, tokens, 0, child_ctx)
84
+ if success:
85
+ if pos < len(tokens):
86
+ print(f"Error parsing: Trailing data at token {pos} ('{tokens[pos].value}')")
87
+ return 0
88
+ return self.execute(final_ctx)
89
+
90
+ if pos > best_fail_pos:
91
+ best_fail_pos = pos
92
+ best_fail_node = last_node
93
+
94
+ err_token = tokens[best_fail_pos].value if best_fail_pos < len(tokens) else "EOF"
95
+ paths = self.get_valid_paths(best_fail_node)
96
+ print(f"Command Error at '{err_token}'. Expected one of: {', '.join(paths)}")
97
+ return 0
98
+
99
+ def _resolve_args(self, args: dict, ctx: ExecutionContext) -> dict:
100
+ resolved_args = {}
101
+ for key, val in args.items():
102
+ if isinstance(val, TargetSelectorData):
103
+ targets = resolve_target_selector(val, ctx)
104
+ if not targets and not val.is_pseudo:
105
+ print(f"No entity was found for selector {val.base}")
106
+ resolved_args[key] = targets
107
+ else:
108
+ resolved_args[key] = val
109
+ return resolved_args
110
+
111
+ def _run_with_modifiers(self, parse_ctx: ParseContext, ctx: ExecutionContext, mod_idx: int) -> int:
112
+ if mod_idx >= len(parse_ctx.modifiers):
113
+ if parse_ctx.executable:
114
+ resolved_args = self._resolve_args(parse_ctx.arguments, ctx)
115
+ return parse_ctx.executable(ctx, **resolved_args)
116
+ return 0
117
+
118
+ modifier, args = parse_ctx.modifiers[mod_idx]
119
+ resolved_args = self._resolve_args(args, ctx)
120
+
121
+ contexts = modifier.modify(ctx, resolved_args)
122
+
123
+ total_res = 0
124
+ for sub_ctx in contexts:
125
+ res = self._run_with_modifiers(parse_ctx, sub_ctx, mod_idx + 1)
126
+ modifier.on_result(sub_ctx, resolved_args, res)
127
+ total_res += res
128
+
129
+ return total_res
130
+
131
+ def execute(self, parse_ctx: ParseContext) -> int:
132
+ return self._run_with_modifiers(parse_ctx, parse_ctx.ctx, 0)
133
+
134
+
135
+ dispatcher = CommandDispatcher()
@@ -0,0 +1,77 @@
1
+ from typing import List
2
+
3
+ from .arguments import TargetSelectorData
4
+ from ..context import ExecutionContext
5
+
6
+
7
+ class ExecuteModifier:
8
+ def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
9
+ raise NotImplementedError()
10
+
11
+ def on_result(self, ctx: ExecutionContext, args: dict, result: int):
12
+ pass
13
+
14
+
15
+ class ExecuteAsModifier(ExecuteModifier):
16
+ def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
17
+ targets = args.get("target", [])
18
+ contexts = []
19
+ for t in targets:
20
+ entity = next((e for e in ctx.world.entities if e.uuid == t or e.name == t), None)
21
+ if entity:
22
+ new_ctx = ctx.clone()
23
+ new_ctx.executor = entity
24
+ contexts.append(new_ctx)
25
+ return contexts
26
+
27
+
28
+ class ExecuteAtModifier(ExecuteModifier):
29
+ def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
30
+ targets = args.get("target", [])
31
+ contexts = []
32
+ for t in targets:
33
+ entity = next((e for e in ctx.world.entities if e.uuid == t or e.name == t), None)
34
+ if entity:
35
+ new_ctx = ctx.clone()
36
+ new_ctx.position = entity.pos
37
+ contexts.append(new_ctx)
38
+ return contexts
39
+
40
+
41
+ class ExecuteStoreScoreModifier(ExecuteModifier):
42
+ def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
43
+ return [ctx]
44
+
45
+ def on_result(self, ctx: ExecutionContext, args: dict, result: int):
46
+ targets = args.get("store_target", [])
47
+ objective = args.get("store_objective", "")
48
+ for t in targets:
49
+ ctx.world.set_score(t, objective, int(result))
50
+
51
+
52
+ class ExecuteStoreResultModifier(ExecuteModifier):
53
+ def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
54
+ return [ctx]
55
+
56
+ def on_result(self, ctx: ExecutionContext, args: dict, result: int):
57
+ targets = args.get("store_target", [])
58
+ store_type = args.get("store_type", "entity")
59
+ path = args.get("store_path", "")
60
+ multiplier = float(args.get("store_multiplier", 1.0))
61
+ datatype = args.get("store_datatype", "int")
62
+
63
+ from ..commands.data import set_nested_dict
64
+ if "NBTType" in store_type:
65
+ store_type = store_type.split(".")[-1].lower()
66
+
67
+ scaled_res = result * multiplier
68
+ if "int" in datatype.lower():
69
+ scaled_res = int(scaled_res)
70
+ elif "byte" in datatype.lower():
71
+ scaled_res = int(scaled_res) & 0xFF
72
+
73
+ for t in targets:
74
+ target_key = f"{store_type}:{t}"
75
+ if target_key not in ctx.world.nbt_storage:
76
+ ctx.world.nbt_storage[target_key] = {}
77
+ set_nested_dict(ctx.world.nbt_storage[target_key], path, scaled_res)
@@ -0,0 +1,15 @@
1
+ from typing import List, Any, Tuple
2
+
3
+ from .nodes import CommandNode
4
+ from ..tokenizer import Token
5
+
6
+
7
+ class ExecuteNode(CommandNode):
8
+ def get_name(self) -> str:
9
+ return "execute"
10
+
11
+ def get_usage(self) -> str:
12
+ return "execute <subcommands> run <command>"
13
+
14
+ def parse(self, tokens: List[Token], pos: int) -> Tuple[Any, int]:
15
+ pass
@@ -0,0 +1,68 @@
1
+ from typing import List, Dict, Callable, Any, Tuple
2
+
3
+ from .arguments import ArgumentType, CommandSyntaxError
4
+
5
+
6
+ class CommandNode:
7
+ def __init__(self):
8
+ self.children: Dict[str, "CommandNode"] = {}
9
+ self.executable: Callable = None
10
+ self.redirect: "CommandNode" = None
11
+ self.modifier: Any = None
12
+
13
+ def then(self, node: "CommandNode") -> "CommandNode":
14
+ self.children[node.get_name()] = node
15
+ return self
16
+
17
+ def executes(self, func: Callable) -> "CommandNode":
18
+ self.executable = func
19
+ return self
20
+
21
+ def forks(self, node: "CommandNode", modifier: Any = None) -> "CommandNode":
22
+ self.redirect = node
23
+ self.modifier = modifier
24
+ return self
25
+
26
+ def parse(self, tokens: List[Any], pos: int) -> Tuple[Any, int]:
27
+ raise NotImplementedError()
28
+
29
+ def get_name(self) -> str:
30
+ raise NotImplementedError()
31
+
32
+ def get_usage(self) -> str:
33
+ raise NotImplementedError()
34
+
35
+
36
+ class LiteralNode(CommandNode):
37
+ def __init__(self, literal: str):
38
+ super().__init__()
39
+ self.literal = literal
40
+
41
+ def parse(self, tokens: List[Any], pos: int) -> Tuple[Any, int]:
42
+ if pos >= len(tokens):
43
+ raise CommandSyntaxError(f"Expected '{self.literal}'")
44
+ if tokens[pos].value == self.literal:
45
+ return self.literal, pos + 1
46
+ raise CommandSyntaxError(f"Expected '{self.literal}', got '{tokens[pos].value}'")
47
+
48
+ def get_name(self) -> str:
49
+ return self.literal
50
+
51
+ def get_usage(self) -> str:
52
+ return self.literal
53
+
54
+
55
+ class ArgumentNode(CommandNode):
56
+ def __init__(self, name: str, arg_type: ArgumentType):
57
+ super().__init__()
58
+ self.name = name
59
+ self.arg_type = arg_type
60
+
61
+ def parse(self, tokens: List[Any], pos: int) -> Tuple[Any, int]:
62
+ return self.arg_type.parse(tokens, pos)
63
+
64
+ def get_name(self) -> str:
65
+ return self.name
66
+
67
+ def get_usage(self) -> str:
68
+ return f"<{self.name}: {self.arg_type.get_name()}>"
@@ -0,0 +1,13 @@
1
+ from . import data
2
+ from . import execute
3
+ from . import function_cmds
4
+ from . import kill
5
+ from . import schedule
6
+ from . import scoreboard
7
+ from . import summon
8
+ from . import tag
9
+ from . import tick
10
+ from . import tp
11
+ from ..command_tree.dispatcher import dispatcher
12
+
13
+ __all__ = ["dispatcher"]