Mercurial > hg > isophonics-drupal-site
comparison vendor/consolidation/annotated-command/src/CommandFileDiscovery.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 129ea1e6d783 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 namespace Consolidation\AnnotatedCommand; | |
3 | |
4 use Symfony\Component\Finder\Finder; | |
5 | |
6 /** | |
7 * Do discovery presuming that the namespace of the command will | |
8 * contain the last component of the path. This is the convention | |
9 * that should be used when searching for command files that are | |
10 * bundled with the modules of a framework. The convention used | |
11 * is that the namespace for a module in a framework should start with | |
12 * the framework name followed by the module name. | |
13 * | |
14 * For example, if base namespace is "Drupal", then a command file in | |
15 * modules/default_content/src/CliTools/ExampleCommands.cpp | |
16 * will be in the namespace Drupal\default_content\CliTools. | |
17 * | |
18 * For global locations, the middle component of the namespace is | |
19 * omitted. For example, if the base namespace is "Drupal", then | |
20 * a command file in __DRUPAL_ROOT__/CliTools/ExampleCommands.cpp | |
21 * will be in the namespace Drupal\CliTools. | |
22 * | |
23 * To discover namespaced commands in modules: | |
24 * | |
25 * $commandFiles = $discovery->discoverNamespaced($moduleList, '\Drupal'); | |
26 * | |
27 * To discover global commands: | |
28 * | |
29 * $commandFiles = $discovery->discover($drupalRoot, '\Drupal'); | |
30 */ | |
31 class CommandFileDiscovery | |
32 { | |
33 /** @var string[] */ | |
34 protected $excludeList; | |
35 /** @var string[] */ | |
36 protected $searchLocations; | |
37 /** @var string */ | |
38 protected $searchPattern = '*Commands.php'; | |
39 /** @var boolean */ | |
40 protected $includeFilesAtBase = true; | |
41 /** @var integer */ | |
42 protected $searchDepth = 2; | |
43 /** @var bool */ | |
44 protected $followLinks = false; | |
45 | |
46 public function __construct() | |
47 { | |
48 $this->excludeList = ['Exclude']; | |
49 $this->searchLocations = [ | |
50 'Command', | |
51 'CliTools', // TODO: Maybe remove | |
52 ]; | |
53 } | |
54 | |
55 /** | |
56 * Specify whether to search for files at the base directory | |
57 * ($directoryList parameter to discover and discoverNamespaced | |
58 * methods), or only in the directories listed in the search paths. | |
59 * | |
60 * @param boolean $includeFilesAtBase | |
61 */ | |
62 public function setIncludeFilesAtBase($includeFilesAtBase) | |
63 { | |
64 $this->includeFilesAtBase = $includeFilesAtBase; | |
65 return $this; | |
66 } | |
67 | |
68 /** | |
69 * Set the list of excludes to add to the finder, replacing | |
70 * whatever was there before. | |
71 * | |
72 * @param array $excludeList The list of directory names to skip when | |
73 * searching for command files. | |
74 */ | |
75 public function setExcludeList($excludeList) | |
76 { | |
77 $this->excludeList = $excludeList; | |
78 return $this; | |
79 } | |
80 | |
81 /** | |
82 * Add one more location to the exclude list. | |
83 * | |
84 * @param string $exclude One directory name to skip when searching | |
85 * for command files. | |
86 */ | |
87 public function addExclude($exclude) | |
88 { | |
89 $this->excludeList[] = $exclude; | |
90 return $this; | |
91 } | |
92 | |
93 /** | |
94 * Set the search depth. By default, fills immediately in the | |
95 * base directory are searched, plus all of the search locations | |
96 * to this specified depth. If the search locations is set to | |
97 * an empty array, then the base directory is searched to this | |
98 * depth. | |
99 */ | |
100 public function setSearchDepth($searchDepth) | |
101 { | |
102 $this->searchDepth = $searchDepth; | |
103 return $this; | |
104 } | |
105 | |
106 /** | |
107 * Specify that the discovery object should follow symlinks. By | |
108 * default, symlinks are not followed. | |
109 */ | |
110 public function followLinks($followLinks = true) | |
111 { | |
112 $this->followLinks = $followLinks; | |
113 return $this; | |
114 } | |
115 | |
116 /** | |
117 * Set the list of search locations to examine in each directory where | |
118 * command files may be found. This replaces whatever was there before. | |
119 * | |
120 * @param array $searchLocations The list of locations to search for command files. | |
121 */ | |
122 public function setSearchLocations($searchLocations) | |
123 { | |
124 $this->searchLocations = $searchLocations; | |
125 return $this; | |
126 } | |
127 | |
128 /** | |
129 * Add one more location to the search location list. | |
130 * | |
131 * @param string $location One more relative path to search | |
132 * for command files. | |
133 */ | |
134 public function addSearchLocation($location) | |
135 { | |
136 $this->searchLocations[] = $location; | |
137 return $this; | |
138 } | |
139 | |
140 /** | |
141 * Specify the pattern / regex used by the finder to search for | |
142 * command files. | |
143 */ | |
144 public function setSearchPattern($searchPattern) | |
145 { | |
146 $this->searchPattern = $searchPattern; | |
147 return $this; | |
148 } | |
149 | |
150 /** | |
151 * Given a list of directories, e.g. Drupal modules like: | |
152 * | |
153 * core/modules/block | |
154 * core/modules/dblog | |
155 * modules/default_content | |
156 * | |
157 * Discover command files in any of these locations. | |
158 * | |
159 * @param string|string[] $directoryList Places to search for commands. | |
160 * | |
161 * @return array | |
162 */ | |
163 public function discoverNamespaced($directoryList, $baseNamespace = '') | |
164 { | |
165 return $this->discover($this->convertToNamespacedList((array)$directoryList), $baseNamespace); | |
166 } | |
167 | |
168 /** | |
169 * Given a simple list containing paths to directories, where | |
170 * the last component of the path should appear in the namespace, | |
171 * after the base namespace, this function will return an | |
172 * associative array mapping the path's basename (e.g. the module | |
173 * name) to the directory path. | |
174 * | |
175 * Module names must be unique. | |
176 * | |
177 * @param string[] $directoryList A list of module locations | |
178 * | |
179 * @return array | |
180 */ | |
181 public function convertToNamespacedList($directoryList) | |
182 { | |
183 $namespacedArray = []; | |
184 foreach ((array)$directoryList as $directory) { | |
185 $namespacedArray[basename($directory)] = $directory; | |
186 } | |
187 return $namespacedArray; | |
188 } | |
189 | |
190 /** | |
191 * Search for command files in the specified locations. This is the function that | |
192 * should be used for all locations that are NOT modules of a framework. | |
193 * | |
194 * @param string|string[] $directoryList Places to search for commands. | |
195 * @return array | |
196 */ | |
197 public function discover($directoryList, $baseNamespace = '') | |
198 { | |
199 $commandFiles = []; | |
200 foreach ((array)$directoryList as $key => $directory) { | |
201 $itemsNamespace = $this->joinNamespace([$baseNamespace, $key]); | |
202 $commandFiles = array_merge( | |
203 $commandFiles, | |
204 $this->discoverCommandFiles($directory, $itemsNamespace), | |
205 $this->discoverCommandFiles("$directory/src", $itemsNamespace) | |
206 ); | |
207 } | |
208 return $commandFiles; | |
209 } | |
210 | |
211 /** | |
212 * Search for command files in specific locations within a single directory. | |
213 * | |
214 * In each location, we will accept only a few places where command files | |
215 * can be found. This will reduce the need to search through many unrelated | |
216 * files. | |
217 * | |
218 * The default search locations include: | |
219 * | |
220 * . | |
221 * CliTools | |
222 * src/CliTools | |
223 * | |
224 * The pattern we will look for is any file whose name ends in 'Commands.php'. | |
225 * A list of paths to found files will be returned. | |
226 */ | |
227 protected function discoverCommandFiles($directory, $baseNamespace) | |
228 { | |
229 $commandFiles = []; | |
230 // In the search location itself, we will search for command files | |
231 // immediately inside the directory only. | |
232 if ($this->includeFilesAtBase) { | |
233 $commandFiles = $this->discoverCommandFilesInLocation( | |
234 $directory, | |
235 $this->getBaseDirectorySearchDepth(), | |
236 $baseNamespace | |
237 ); | |
238 } | |
239 | |
240 // In the other search locations, | |
241 foreach ($this->searchLocations as $location) { | |
242 $itemsNamespace = $this->joinNamespace([$baseNamespace, $location]); | |
243 $commandFiles = array_merge( | |
244 $commandFiles, | |
245 $this->discoverCommandFilesInLocation( | |
246 "$directory/$location", | |
247 $this->getSearchDepth(), | |
248 $itemsNamespace | |
249 ) | |
250 ); | |
251 } | |
252 return $commandFiles; | |
253 } | |
254 | |
255 /** | |
256 * Return a Finder search depth appropriate for our selected search depth. | |
257 * | |
258 * @return string | |
259 */ | |
260 protected function getSearchDepth() | |
261 { | |
262 return $this->searchDepth <= 0 ? '== 0' : '<= ' . $this->searchDepth; | |
263 } | |
264 | |
265 /** | |
266 * Return a Finder search depth for the base directory. If the | |
267 * searchLocations array has been populated, then we will only search | |
268 * for files immediately inside the base directory; no traversal into | |
269 * deeper directories will be done, as that would conflict with the | |
270 * specification provided by the search locations. If there is no | |
271 * search location, then we will search to whatever depth was specified | |
272 * by the client. | |
273 * | |
274 * @return string | |
275 */ | |
276 protected function getBaseDirectorySearchDepth() | |
277 { | |
278 if (!empty($this->searchLocations)) { | |
279 return '== 0'; | |
280 } | |
281 return $this->getSearchDepth(); | |
282 } | |
283 | |
284 /** | |
285 * Search for command files in just one particular location. Returns | |
286 * an associative array mapping from the pathname of the file to the | |
287 * classname that it contains. The pathname may be ignored if the search | |
288 * location is included in the autoloader. | |
289 * | |
290 * @param string $directory The location to search | |
291 * @param string $depth How deep to search (e.g. '== 0' or '< 2') | |
292 * @param string $baseNamespace Namespace to prepend to each classname | |
293 * | |
294 * @return array | |
295 */ | |
296 protected function discoverCommandFilesInLocation($directory, $depth, $baseNamespace) | |
297 { | |
298 if (!is_dir($directory)) { | |
299 return []; | |
300 } | |
301 $finder = $this->createFinder($directory, $depth); | |
302 | |
303 $commands = []; | |
304 foreach ($finder as $file) { | |
305 $relativePathName = $file->getRelativePathname(); | |
306 $relativeNamespaceAndClassname = str_replace( | |
307 ['/', '.php'], | |
308 ['\\', ''], | |
309 $relativePathName | |
310 ); | |
311 $classname = $this->joinNamespace([$baseNamespace, $relativeNamespaceAndClassname]); | |
312 $commandFilePath = $this->joinPaths([$directory, $relativePathName]); | |
313 $commands[$commandFilePath] = $classname; | |
314 } | |
315 | |
316 return $commands; | |
317 } | |
318 | |
319 /** | |
320 * Create a Finder object for use in searching a particular directory | |
321 * location. | |
322 * | |
323 * @param string $directory The location to search | |
324 * @param string $depth The depth limitation | |
325 * | |
326 * @return Finder | |
327 */ | |
328 protected function createFinder($directory, $depth) | |
329 { | |
330 $finder = new Finder(); | |
331 $finder->files() | |
332 ->name($this->searchPattern) | |
333 ->in($directory) | |
334 ->depth($depth); | |
335 | |
336 foreach ($this->excludeList as $item) { | |
337 $finder->exclude($item); | |
338 } | |
339 | |
340 if ($this->followLinks) { | |
341 $finder->followLinks(); | |
342 } | |
343 | |
344 return $finder; | |
345 } | |
346 | |
347 /** | |
348 * Combine the items of the provied array into a backslash-separated | |
349 * namespace string. Empty and numeric items are omitted. | |
350 * | |
351 * @param array $namespaceParts List of components of a namespace | |
352 * | |
353 * @return string | |
354 */ | |
355 protected function joinNamespace(array $namespaceParts) | |
356 { | |
357 return $this->joinParts( | |
358 '\\', | |
359 $namespaceParts, | |
360 function ($item) { | |
361 return !is_numeric($item) && !empty($item); | |
362 } | |
363 ); | |
364 } | |
365 | |
366 /** | |
367 * Combine the items of the provied array into a slash-separated | |
368 * pathname. Empty items are omitted. | |
369 * | |
370 * @param array $pathParts List of components of a path | |
371 * | |
372 * @return string | |
373 */ | |
374 protected function joinPaths(array $pathParts) | |
375 { | |
376 $path = $this->joinParts( | |
377 '/', | |
378 $pathParts, | |
379 function ($item) { | |
380 return !empty($item); | |
381 } | |
382 ); | |
383 return str_replace(DIRECTORY_SEPARATOR, '/', $path); | |
384 } | |
385 | |
386 /** | |
387 * Simple wrapper around implode and array_filter. | |
388 * | |
389 * @param string $delimiter | |
390 * @param array $parts | |
391 * @param callable $filterFunction | |
392 */ | |
393 protected function joinParts($delimiter, $parts, $filterFunction) | |
394 { | |
395 $parts = array_map( | |
396 function ($item) use ($delimiter) { | |
397 return rtrim($item, $delimiter); | |
398 }, | |
399 $parts | |
400 ); | |
401 return implode( | |
402 $delimiter, | |
403 array_filter($parts, $filterFunction) | |
404 ); | |
405 } | |
406 } |