PocketMine-MP 5.37.2 git-e507eb5e50da3ead3ae88ed2324df21e75820019
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
52 private const FORMAT_STRING_REGEX = '/\G\$(\$)?((?!0)+\d+)(-)?/';
53
57 public function __construct(
58 string $namespace,
59 string $name,
60 private array $formatStrings
61 ){
62 parent::__construct($namespace, $name, KnownTranslationFactory::pocketmine_command_userDefined_description());
63 }
64
65 public function execute(CommandSender $sender, string $commandLabel, array $args){
66 $commands = [];
67 $result = true;
68
69 foreach($this->formatStrings as $formatString){
70 try{
71 $formatArgs = CommandStringHelper::parseQuoteAware($formatString);
72 $unresolved = [];
73 $processedArgs = [];
74 foreach($formatArgs as $formatArg){
75 $processedArg = $this->buildCommand($formatArg, $args);
76 if($processedArg === null){
77 $unresolved[] = $formatArg;
78 }elseif(count($unresolved) !== 0){
79 //unresolved args are OK only if they are at the end of the string - we can't have holes in the args list
80 throw new \InvalidArgumentException("Unable to resolve format arguments (" . implode(", ", $unresolved) . ") in command string \"$formatString\" due to missing arguments");
81 }else{
82 $processedArgs[] = $processedArg;
83 }
84 }
85 $commands[] = $processedArgs;
86 }catch(\InvalidArgumentException $e){
87 $sender->sendMessage(TextFormat::RED . $e->getMessage());
88 return false;
89 }
90 }
91
92 $commandMap = $sender->getServer()->getCommandMap();
93 foreach($commands as $commandArgs){
94 //this approximately duplicates the logic found in SimpleCommandMap::dispatch()
95 //this is to allow directly invoking the commands without having to rebuild a command string and parse it
96 //again for no reason
97 //TODO: a method on CommandMap to invoke a command with pre-parsed arguments would probably be a good idea
98 //for a future major version
99 $commandLabel = array_shift($commandArgs);
100 if($commandLabel === null){
101 throw new AssumptionFailedError("This should have been checked before construction");
102 }
103
104 //formatted command aliases don't use user-specific aliases since they are globally defined in pocketmine.yml
105 //using user-specific aliases might break the behaviour
106 if(($target = $commandMap->getCommand($commandLabel)) instanceof Command){
107
108 $timings = Timings::getCommandDispatchTimings($target->getId());
109 $timings->startTiming();
110
111 try{
112 $target->execute($sender, $commandLabel, $commandArgs);
114 $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage() ?? "/$commandLabel")));
115 }finally{
116 $timings->stopTiming();
117 }
118 }else{
119 //TODO: this seems suspicious - why do we continue alias execution if one of the commands is borked?
120 $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->prefix(TextFormat::RED)));
121
122 //to match the behaviour of SimpleCommandMap::dispatch()
123 //this shouldn't normally happen, but might happen if the command was unregistered or modified after
124 //the alias was installed
125 $result = false;
126 }
127 }
128
129 return $result;
130 }
131
136 private function buildCommand(string $formatString, array $args) : ?string{
137 $index = 0;
138 while(($index = strpos($formatString, '$', $index)) !== false){
139 $start = $index;
140 if($index > 0 && $formatString[$start - 1] === "\\"){
141 $formatString = substr($formatString, 0, $start - 1) . substr($formatString, $start);
142 //offset is now pointing at the next character because we just deleted the \
143 continue;
144 }
145
146 $info = self::extractPlaceholderInfo($formatString, $index);
147 if($info === null){
148 throw new \InvalidArgumentException("Invalid replacement token");
149 }
150 [$fullPlaceholder, $required, $position, $rest] = $info;
151 $position--; //array offsets start at 0, but placeholders start at 1
152
153 if($required && $position >= count($args)){
154 throw new \InvalidArgumentException("Missing required argument " . ($position + 1));
155 }
156
157 $replacement = self::buildReplacement($args, $position, $rest);
158 if($replacement === null){
159 return null;
160 }
161
162 $end = $index + strlen($fullPlaceholder);
163 $formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
164
165 $index = $start + strlen($replacement);
166 }
167
168 return $formatString;
169 }
170
175 private static function buildReplacement(array $args, int $position, bool $rest) : ?string{
176 if($rest && $position < count($args)){
177 $replacement = "";
178 for($i = $position, $c = count($args); $i < $c; ++$i){
179 if($i !== $position){
180 $replacement .= " ";
181 }
182
183 $replacement .= $args[$i];
184 }
185
186 return $replacement;
187 }elseif($position < count($args)){
188 return $args[$position];
189 }
190
191 return null;
192 }
193
197 private static function extractPlaceholderInfo(string $commandString, int $offset) : ?array{
198 if(preg_match(self::FORMAT_STRING_REGEX, $commandString, $matches, 0, $offset) !== 1){
199 return null;
200 }
201
202 $fullPlaceholder = $matches[0];
203
204 $required = ($matches[1] ?? "") !== "";
205 $position = (int) $matches[2];
206 $variadic = ($matches[3] ?? "") !== "";
207
208 return [$fullPlaceholder, $required, $position, $variadic];
209 }
210}
execute(CommandSender $sender, string $commandLabel, array $args)
__construct(string $namespace, string $name, private array $formatStrings)