47 private const FORMAT_STRING_REGEX =
'/\G\$(\$)?((?!0)+\d+)(-)?/';
55 private array $formatStrings
57 parent::__construct($namespace, $name, KnownTranslationFactory::pocketmine_command_userDefined_description());
64 foreach($this->formatStrings as $formatString){
66 $formatArgs = CommandStringHelper::parseQuoteAware($formatString);
69 foreach($formatArgs as $formatArg){
70 $processedArg = $this->buildCommand($formatArg, $args);
71 if($processedArg ===
null){
72 $unresolved[] = $formatArg;
73 }elseif(count($unresolved) !== 0){
75 throw new \InvalidArgumentException(
"Unable to resolve format arguments (" . implode(
", ", $unresolved) .
") in command string \"$formatString\" due to missing arguments");
77 $processedArgs[] = $processedArg;
80 $commands[] = $processedArgs;
81 }
catch(\InvalidArgumentException $e){
82 $sender->sendMessage(TextFormat::RED . $e->getMessage());
87 $commandMap = $sender->getServer()->getCommandMap();
88 foreach($commands as $commandArgs){
94 $commandLabel = array_shift($commandArgs);
95 if($commandLabel ===
null){
101 if(($target = $commandMap->getCommand($commandLabel)) instanceof
Command){
103 $timings = Timings::getCommandDispatchTimings($target->getId());
104 $timings->startTiming();
107 $target->execute($sender, $commandLabel, $commandArgs);
109 $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::commands_generic_usage($target->getUsage() ??
"/$commandLabel")));
111 $timings->stopTiming();
115 $sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel,
"/help")->prefix(TextFormat::RED)));
131 private function buildCommand(
string $formatString, array $args) : ?string{
133 while(($index = strpos($formatString,
'$', $index)) !==
false){
135 if($index > 0 && $formatString[$start - 1] ===
"\\"){
136 $formatString = substr($formatString, 0, $start - 1) . substr($formatString, $start);
141 $info = self::extractPlaceholderInfo($formatString, $index);
143 throw new \InvalidArgumentException(
"Invalid replacement token");
145 [$fullPlaceholder, $required, $position, $rest] = $info;
148 if($required && $position >= count($args)){
149 throw new \InvalidArgumentException(
"Missing required argument " . ($position + 1));
152 $replacement = self::buildReplacement($args, $position, $rest);
153 if($replacement ===
null){
157 $end = $index + strlen($fullPlaceholder);
158 $formatString = substr($formatString, 0, $start) . $replacement . substr($formatString, $end);
160 $index = $start + strlen($replacement);
163 return $formatString;
170 private static function buildReplacement(array $args,
int $position,
bool $rest) : ?string{
171 if($rest && $position < count($args)){
173 for($i = $position, $c = count($args); $i < $c; ++$i){
174 if($i !== $position){
178 $replacement .= $args[$i];
182 }elseif($position < count($args)){
183 return $args[$position];
192 private static function extractPlaceholderInfo(
string $commandString,
int $offset) : ?array{
193 if(preg_match(self::FORMAT_STRING_REGEX, $commandString, $matches, 0, $offset) !== 1){
197 $fullPlaceholder = $matches[0];
199 $required = ($matches[1] ??
"") !==
"";
200 $position = (int) $matches[2];
201 $variadic = ($matches[3] ??
"") !==
"";
203 return [$fullPlaceholder, $required, $position, $variadic];