@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
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}