1 | <?php |
||
44 | abstract class Binary |
||
45 | { |
||
46 | /** |
||
47 | * The file system path to a VCS binary |
||
48 | * |
||
49 | * @var string |
||
50 | */ |
||
51 | protected $path; |
||
52 | |||
53 | /** |
||
54 | * Ensures that the given arguments is a valid VCS binary |
||
55 | * |
||
56 | * @param Binary|string|null $binary The VCS binary |
||
57 | * @return static |
||
58 | * @throws \InvalidArgumentException If $binary is not a valid VCS binary |
||
59 | */ |
||
60 | 197 | public static function ensure($binary) |
|
61 | { |
||
62 | 197 | if ($binary === null || is_string($binary)) { |
|
63 | $binary = new static($binary); |
||
64 | } |
||
65 | 197 | if (!($binary instanceof static)) { |
|
66 | throw new \InvalidArgumentException( |
||
67 | sprintf( |
||
68 | 'The $binary argument must either be a TQ\Vcs\Binary |
||
69 | instance or a path to the VCS binary (%s given)', |
||
70 | (is_object($binary)) ? get_class($binary) : gettype($binary) |
||
71 | ) |
||
72 | ); |
||
73 | } |
||
74 | 197 | return $binary; |
|
75 | } |
||
76 | |||
77 | |||
78 | /** |
||
79 | * Checks if the current system is Windows |
||
80 | * |
||
81 | * @return boolean True if we're on a Windows machine |
||
82 | */ |
||
83 | 175 | protected static function isWindows() |
|
87 | |||
88 | /** |
||
89 | * Creates a VCS binary interface |
||
90 | * |
||
91 | * @param string $path The path to the VCS binary |
||
92 | * @throws \InvalidArgumentException If no VCS binary is found |
||
93 | */ |
||
94 | 191 | public function __construct($path) |
|
101 | |||
102 | /** |
||
103 | * Create a call to the VCS binary for later execution |
||
104 | * |
||
105 | * @param string $path The full path to the VCS repository |
||
106 | * @param string $command The VCS command, e.g. show, commit or add |
||
107 | * @param array $arguments The command arguments |
||
108 | * @return Call |
||
109 | */ |
||
110 | 175 | public function createCall($path, $command, array $arguments) |
|
111 | { |
||
112 | 175 | if (!self::isWindows()) { |
|
113 | 175 | $binary = escapeshellcmd($this->path); |
|
114 | } else { |
||
115 | $binary = $this->path; |
||
116 | } |
||
117 | 175 | if (!empty($command)) { |
|
118 | 173 | $command = escapeshellarg($command); |
|
119 | } |
||
120 | |||
121 | 175 | list($args, $files) = $this->sanitizeCommandArguments($arguments); |
|
122 | 175 | $cmd = $this->createCallCommand($binary, $command, $args, $files); |
|
123 | 175 | $call = $this->doCreateCall($cmd, $path); |
|
124 | 175 | return $call; |
|
125 | } |
||
126 | |||
127 | /** |
||
128 | * The call factory |
||
129 | * |
||
130 | * @param string $cmd The command string to be executed |
||
131 | * @param string $path The working directory |
||
132 | * @return Call |
||
133 | */ |
||
134 | 175 | protected function doCreateCall($cmd, $path) |
|
138 | |||
139 | /** |
||
140 | * Creates the command string to be executed |
||
141 | * |
||
142 | * @param string $binary The path to the binary |
||
143 | * @param string $command The VCS command |
||
144 | * @param array $args The list of command line arguments (sanitized) |
||
145 | * @param array $files The list of files to be added to the command line call |
||
146 | * @return string The command string to be executed |
||
147 | */ |
||
148 | 175 | protected function createCallCommand($binary, $command, array $args, array $files) |
|
149 | { |
||
150 | 175 | $cmd = trim(sprintf('%s %s %s', $binary, $command, implode(' ', $args))); |
|
151 | 175 | if (count($files) > 0) { |
|
152 | 113 | $cmd .= ' -- '.implode(' ', $files); |
|
153 | } |
||
154 | 175 | return $cmd; |
|
155 | } |
||
156 | |||
157 | /** |
||
158 | * Sanitizes a command line argument |
||
159 | * |
||
160 | * @param string $key The argument key |
||
161 | * @param string $value The argument value (can be empty) |
||
162 | * @return string |
||
163 | */ |
||
164 | 160 | protected function sanitizeCommandArgument($key, $value) |
|
165 | { |
||
166 | 160 | $key = ltrim($key, '-'); |
|
167 | 160 | if (strlen($key) == 1 || is_numeric($key)) { |
|
168 | 66 | $arg = sprintf('-%s', escapeshellarg($key)); |
|
169 | 66 | if ($value !== null) { |
|
170 | 66 | $arg .= ' '.escapeshellarg($value); |
|
171 | } |
||
172 | } else { |
||
173 | 156 | $arg = sprintf('--%s', escapeshellarg($key)); |
|
174 | 156 | if ($value !== null) { |
|
175 | 121 | $arg .= '='.escapeshellarg($value); |
|
176 | } |
||
177 | } |
||
178 | 160 | return $arg; |
|
179 | } |
||
180 | |||
181 | /** |
||
182 | * Sanitizes a list of command line arguments and splits them into args and files |
||
183 | * |
||
184 | * @param array $arguments The list of arguments |
||
185 | * @return array An array with (args, files) |
||
186 | */ |
||
187 | 175 | protected function sanitizeCommandArguments(array $arguments) |
|
188 | { |
||
189 | 175 | $args = array(); |
|
190 | 175 | $files = array(); |
|
191 | 175 | $fileMode = false; |
|
192 | 175 | foreach ($arguments as $k => $v) { |
|
193 | 171 | if ($v === '--' || $k === '--') { |
|
194 | 115 | $fileMode = true; |
|
195 | 115 | continue; |
|
196 | } |
||
197 | 169 | if (is_int($k)) { |
|
198 | 165 | if (strpos($v, '-') === 0) { |
|
199 | 155 | $args[] = $this->sanitizeCommandArgument($v, null); |
|
200 | 140 | } else if ($fileMode) { |
|
201 | 113 | $files[] = escapeshellarg($v); |
|
202 | } else { |
||
203 | 165 | $args[] = escapeshellarg($v); |
|
204 | } |
||
205 | } else { |
||
206 | 123 | if (strpos($k, '-') === 0) { |
|
207 | 169 | $args[] = $this->sanitizeCommandArgument($k, $v); |
|
208 | } |
||
209 | } |
||
210 | } |
||
211 | 175 | return array($args, $files); |
|
212 | } |
||
213 | |||
214 | /** |
||
215 | * Extracts the CLI call parameters from the arguments to a magic method call |
||
216 | * |
||
217 | * @param string $method The VCS command, e.g. show, commit or add |
||
218 | * @param array $arguments The command arguments with the path to the VCS |
||
219 | * repository being the first argument |
||
220 | * @return array An array with (path, method, args, stdIn) |
||
221 | * @throws \InvalidArgumentException If the method is called with less than one argument |
||
222 | */ |
||
223 | 153 | protected function extractCallParametersFromMagicCall($method, array $arguments) |
|
224 | { |
||
225 | 153 | if (count($arguments) < 1) { |
|
226 | throw new \InvalidArgumentException(sprintf( |
||
227 | '"%s" must be called with at least one argument denoting the path', $method |
||
228 | )); |
||
229 | } |
||
230 | |||
231 | 153 | $path = array_shift($arguments); |
|
232 | 153 | $args = array(); |
|
233 | 153 | $stdIn = null; |
|
234 | |||
235 | 153 | if (count($arguments) > 0) { |
|
236 | 153 | $args = array_shift($arguments); |
|
237 | 153 | if (!is_array($args)) { |
|
238 | 1 | $args = array($args); |
|
239 | } |
||
240 | |||
241 | 153 | if (count($arguments) > 0) { |
|
242 | 6 | $stdIn = array_shift($arguments); |
|
243 | 6 | if (!is_string($stdIn)) { |
|
244 | $stdIn = null; |
||
245 | } |
||
246 | } |
||
247 | } |
||
248 | 153 | return array($path, $method, $args, $stdIn); |
|
249 | } |
||
250 | |||
251 | /** |
||
252 | * Method overloading - allows calling VCS commands directly as class methods |
||
253 | * |
||
254 | * @param string $method The VCS command, e.g. show, commit or add |
||
255 | * @param array $arguments The command arguments with the path to the VCS |
||
256 | * repository being the first argument |
||
257 | * @return CallResult |
||
258 | */ |
||
259 | 153 | public function __call($method, array $arguments) |
|
266 | } |
||
267 | |||
268 |