@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 215 lines 6.3 kB view raw
1<?php 2 3final class PhabricatorChartFunctionArgumentParser 4 extends Phobject { 5 6 private $function; 7 private $rawArguments; 8 private $unconsumedArguments; 9 private $haveAllArguments = false; 10 private $unparsedArguments; 11 private $argumentMap = array(); 12 private $argumentPosition = 0; 13 private $argumentValues = array(); 14 private $repeatableArgument = null; 15 16 public function setFunction(PhabricatorChartFunction $function) { 17 $this->function = $function; 18 return $this; 19 } 20 21 public function getFunction() { 22 return $this->function; 23 } 24 25 public function setRawArguments(array $arguments) { 26 $this->rawArguments = $arguments; 27 $this->unconsumedArguments = $arguments; 28 } 29 30 public function addArgument(PhabricatorChartFunctionArgument $spec) { 31 $name = $spec->getName(); 32 if (!strlen($name)) { 33 throw new Exception( 34 pht( 35 'Chart function "%s" emitted an argument specification with no '. 36 'argument name. Argument specifications must have unique names.', 37 $this->getFunctionArgumentSignature())); 38 } 39 40 $type = $spec->getType(); 41 if (!strlen($type)) { 42 throw new Exception( 43 pht( 44 'Chart function "%s" emitted an argument specification ("%s") with '. 45 'no type. Each argument specification must have a valid type.', 46 $this->getFunctionArgumentSignature(), 47 $name)); 48 } 49 50 if (isset($this->argumentMap[$name])) { 51 throw new Exception( 52 pht( 53 'Chart function "%s" emitted multiple argument specifications '. 54 'with the same name ("%s"). Each argument specification must have '. 55 'a unique name.', 56 $this->getFunctionArgumentSignature(), 57 $name)); 58 } 59 60 if ($this->repeatableArgument) { 61 if ($spec->getRepeatable()) { 62 throw new Exception( 63 pht( 64 'Chart function "%s" emitted multiple repeatable argument '. 65 'specifications ("%s" and "%s"). Only one argument may be '. 66 'repeatable and it must be the last argument.', 67 $this->getFunctionArgumentSignature(), 68 $name, 69 $this->repeatableArgument->getName())); 70 } else { 71 throw new Exception( 72 pht( 73 'Chart function "%s" emitted a repeatable argument ("%s"), then '. 74 'another argument ("%s"). No arguments are permitted after a '. 75 'repeatable argument.', 76 $this->getFunctionArgumentSignature(), 77 $this->repeatableArgument->getName(), 78 $name)); 79 } 80 } 81 82 if ($spec->getRepeatable()) { 83 $this->repeatableArgument = $spec; 84 } 85 86 $this->argumentMap[$name] = $spec; 87 $this->unparsedArguments[] = $spec; 88 89 return $this; 90 } 91 92 public function parseArgument( 93 PhabricatorChartFunctionArgument $spec) { 94 $this->addArgument($spec); 95 return $this->parseArguments(); 96 } 97 98 public function setHaveAllArguments($have_all) { 99 $this->haveAllArguments = $have_all; 100 return $this; 101 } 102 103 public function getAllArguments() { 104 return array_values($this->argumentMap); 105 } 106 107 public function getRawArguments() { 108 return $this->rawArguments; 109 } 110 111 public function parseArguments() { 112 $have_count = count($this->rawArguments); 113 $want_count = count($this->argumentMap); 114 115 if ($this->haveAllArguments) { 116 if ($this->repeatableArgument) { 117 if ($want_count > $have_count) { 118 throw new Exception( 119 pht( 120 'Function "%s" expects %s or more argument(s), but only %s '. 121 'argument(s) were provided.', 122 $this->getFunctionArgumentSignature(), 123 $want_count, 124 $have_count)); 125 } 126 } else if ($want_count !== $have_count) { 127 throw new Exception( 128 pht( 129 'Function "%s" expects %s argument(s), but %s argument(s) were '. 130 'provided.', 131 $this->getFunctionArgumentSignature(), 132 $want_count, 133 $have_count)); 134 } 135 } 136 137 while ($this->unparsedArguments) { 138 $argument = array_shift($this->unparsedArguments); 139 $name = $argument->getName(); 140 141 if (!$this->unconsumedArguments) { 142 throw new Exception( 143 pht( 144 'Function "%s" expects at least %s argument(s), but only %s '. 145 'argument(s) were provided.', 146 $this->getFunctionArgumentSignature(), 147 $want_count, 148 $have_count)); 149 } 150 151 $raw_argument = array_shift($this->unconsumedArguments); 152 $this->argumentPosition++; 153 154 $is_repeatable = $argument->getRepeatable(); 155 156 // If this argument is repeatable and we have more arguments, add it 157 // back to the end of the list so we can continue parsing. 158 if ($is_repeatable && $this->unconsumedArguments) { 159 $this->unparsedArguments[] = $argument; 160 } 161 162 try { 163 $value = $argument->newValue($raw_argument); 164 } catch (Exception $ex) { 165 throw new Exception( 166 pht( 167 'Argument "%s" (in position "%s") to function "%s" is '. 168 'invalid: %s', 169 $name, 170 $this->argumentPosition, 171 $this->getFunctionArgumentSignature(), 172 $ex->getMessage())); 173 } 174 175 if ($is_repeatable) { 176 if (!isset($this->argumentValues[$name])) { 177 $this->argumentValues[$name] = array(); 178 } 179 $this->argumentValues[$name][] = $value; 180 } else { 181 $this->argumentValues[$name] = $value; 182 } 183 } 184 } 185 186 public function getArgumentValue($key) { 187 if (!array_key_exists($key, $this->argumentValues)) { 188 throw new Exception( 189 pht( 190 'Function "%s" is requesting an argument ("%s") that it did '. 191 'not define.', 192 $this->getFunctionArgumentSignature(), 193 $key)); 194 } 195 196 return $this->argumentValues[$key]; 197 } 198 199 private function getFunctionArgumentSignature() { 200 $argument_list = array(); 201 foreach ($this->argumentMap as $key => $spec) { 202 $argument_list[] = $key; 203 } 204 205 if (!$this->haveAllArguments || $this->repeatableArgument) { 206 $argument_list[] = '...'; 207 } 208 209 return sprintf( 210 '%s(%s)', 211 $this->getFunction()->getFunctionKey(), 212 implode(', ', $argument_list)); 213 } 214 215}