PocketMine-MP 5.36.1 git-eaa7c4834c8fe2f379d24e7f0ee6cc63cfb18ccc
Loading...
Searching...
No Matches
FormattedCommandAlias.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\command;
25
32use function array_shift;
33use function count;
34use function implode;
35use function preg_match;
36use function strlen;
37use function strpos;
38use function substr;
39
47 private const FORMAT_STRING_REGEX = '/\G\$(\$)?((?!0)+\d+)(-)?/';
48
52 public function __construct(
53 string $namespace,
54 string $name,
55 private array $formatStrings
56 ){
57 parent::__construct($namespace, $name, KnownTranslationFactory::pocketmine_command_userDefined_description());
58 }
59
60 public function execute(CommandSender $sender, string $commandLabel, array $args){
61 $commands = [];
62 $result = true;
63
64 foreach($this->formatStrings as $formatString){
65 try{
66 $formatArgs = CommandStringHelper::parseQuoteAware($formatString);
67 $unresolved = [];
68 $processedArgs = [];
69 foreach($formatArgs as $formatArg){
70 $processedArg = $this->buildCommand($formatArg, $args);
71 if($processedArg === null){
72 $unresolved[] = $formatArg;
73 }elseif(count($unresolved) !== 0){
74 //unresolved args are OK only if they are at the end of the string - we can't have holes in the args list
75 throw new \InvalidArgumentException("Unable to resolve format arguments (" . implode(", ", $unresolved) . ") in command string \"$formatString\" due to missing arguments");
76 }else{
77 $processedArgs[] = $processedArg;
78 }
79 }
80 $commands[] = $processedArgs;
81 }catch(\InvalidArgumentException $e){
82 $sender->sendMessage(TextFormat::RED . $e->getMessage());
83 return false;
84 }
85 }
86
87 $commandMap = $sender->getServer()->getCommandMap();
88 foreach($commands as $commandArgs){
89 //this approximately duplicates the logic found in SimpleCommandMap::dispatch()
90 //this is to allow directly invoking the commands without having to rebuild a command string and parse it
91 //again for no reason
92 //TODO: a method on CommandMap to invoke a command with pre-parsed arguments would probably be a good idea
93 //for a future major version
94 $commandLabel = array_shift($commandArgs);
95 if($commandLabel === null){
96 throw new AssumptionFailedError("This should have been checked before construction");
97 }
98
99 //formatted command aliases don't use user-specific aliases since they are globally defined in pocketmine.yml
100 //using user-specific aliases might break the behaviour
101 if(($target = $commandMap->getCommand($commandLabel)) instanceof Command){
102
103 $timings = Timings::getCommandDispatchTimings($target->getId());
104 $timings->startTiming();
105
106 try{
107 $target->execute($sender, $commandLabel, $commandArgs);
109 $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage() ?? "/$commandLabel")));
110 }finally{
111 $timings->stopTiming();
112 }
113 }else{
114 //TODO: this seems suspicious - why do we continue alias execution if one of the commands is borked?
115 $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->prefix(TextFormat::RED)));
116
117 //to match the behaviour of SimpleCommandMap::dispatch()
118 //this shouldn't normally happen, but might happen if the command was unregistered or modified after
119 //the alias was installed
120 $result = false;
121 }
122 }
123
124 return $result;
125 }
126
131 private function buildCommand(string $formatString, array $args) : ?string{
132 $index = 0;
133 while(($index = strpos($formatString, '$', $index)) !== false){
134 $start = $index;
135 if($index > 0 && $formatString[$start - 1] === "\\"){
136 $formatString = substr($formatString, 0, $start - 1) . substr($formatString, $start);
137 //offset is now pointing at the next character because we just deleted the \
138 continue;
139 }
140
141 $info = self::extractPlaceholderInfo($formatString, $index);
142 if($info === null){
143 throw new \InvalidArgumentException("Invalid replacement token");
144 }
145 [$fullPlaceholder, $required, $position, $rest] = $info;
146 $position--; //array offsets start at 0, but placeholders start at 1
147
148 if($required && $position >= count($args)){
149 throw new \InvalidArgumentException("Missing required argument " . ($position + 1));
150 }
151
152 $replacement = self::buildReplacement($args, $position, $rest);
153 if($replacement === null){
154 return null;
155 }
156
157 $end = $index + strlen($fullPlaceholder);
158 $formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
159
160 $index = $start + strlen($replacement);
161 }
162
163 return $formatString;
164 }
165
170 private static function buildReplacement(array $args, int $position, bool $rest) : ?string{
171 if($rest && $position < count($args)){
172 $replacement = "";
173 for($i = $position, $c = count($args); $i < $c; ++$i){
174 if($i !== $position){
175 $replacement .= " ";
176 }
177
178 $replacement .= $args[$i];
179 }
180
181 return $replacement;
182 }elseif($position < count($args)){
183 return $args[$position];
184 }
185
186 return null;
187 }
188
192 private static function extractPlaceholderInfo(string $commandString, int $offset) : ?array{
193 if(preg_match(self::FORMAT_STRING_REGEX, $commandString, $matches, 0, $offset) !== 1){
194 return null;
195 }
196
197 $fullPlaceholder = $matches[0];
198
199 $required = ($matches[1] ?? "") !== "";
200 $position = (int) $matches[2];
201 $variadic = ($matches[3] ?? "") !== "";
202
203 return [$fullPlaceholder, $required, $position, $variadic];
204 }
205}
execute(CommandSender $sender, string $commandLabel, array $args)
__construct(string $namespace, string $name, private array $formatStrings)