1e3fd57b5e440678935a76992ca8daffac971dd2
[yaffs-website] / vendor / consolidation / robo / RoboFile.php
1 <?php
2 use Symfony\Component\Finder\Finder;
3
4 class RoboFile extends \Robo\Tasks
5 {
6     /**
7      * Run the Robo unit tests.
8      */
9     public function test(array $args, $options =
10         [
11             'coverage-html' => false,
12             'coverage' => false
13         ])
14     {
15         $taskCodecept = $this->taskCodecept()
16             ->args($args);
17
18         if ($options['coverage']) {
19             $taskCodecept->coverageXml('../../build/logs/clover.xml');
20         }
21         if ($options['coverage-html']) {
22             $taskCodecept->coverageHtml('../../build/logs/coverage');
23         }
24
25         return $taskCodecept->run();
26      }
27
28     /**
29      * Code sniffer.
30      *
31      * Run the PHP Codesniffer on a file or directory.
32      *
33      * @param string $file
34      *    A file or directory to analyze.
35      * @option $autofix Whether to run the automatic fixer or not.
36      * @option $strict Show warnings as well as errors.
37      *    Default is to show only errors.
38      */
39     public function sniff(
40         $file = 'src/',
41         $options = [
42             'autofix' => false,
43             'strict' => false,
44         ]
45     ) {
46         $strict = $options['strict'] ? '' : '-n';
47         $result = $this->taskExec("./vendor/bin/phpcs --standard=PSR2 --exclude=Squiz.Classes.ValidClassName {$strict} {$file}")->run();
48         if (!$result->wasSuccessful()) {
49             if (!$options['autofix']) {
50                 $options['autofix'] = $this->confirm('Would you like to run phpcbf to fix the reported errors?');
51             }
52             if ($options['autofix']) {
53                 $result = $this->taskExec("./vendor/bin/phpcbf --standard=PSR2 --exclude=Squiz.Classes.ValidClassName {$file}")->run();
54             }
55         }
56         return $result;
57     }
58
59     /**
60      * Generate a new Robo task that wraps an existing utility class.
61      *
62      * @param $className The name of the existing utility class to wrap.
63      * @param $wrapperClassName The name of the wrapper class to create. Optional.
64      * @usage generate:task 'Symfony\Component\Filesystem\Filesystem' FilesystemStack
65      */
66     public function generateTask($className, $wrapperClassName = "")
67     {
68         return $this->taskGenTask($className, $wrapperClassName)->run();
69     }
70
71     /**
72      * Release Robo.
73      */
74     public function release($opts = ['beta' => false])
75     {
76         $this->checkPharReadonly();
77
78         $version = \Robo\Robo::VERSION;
79         $stable = !$opts['beta'];
80         if ($stable) {
81             $version = preg_replace('/-.*/', '', $version);
82         }
83         else {
84             $version = $this->incrementVersion($version, 'beta');
85         }
86         $this->writeVersion($version);
87         $this->yell("Releasing Robo $version");
88
89         $this->docs();
90         $this->taskGitStack()
91             ->add('-A')
92             ->commit("Robo release $version")
93             ->pull()
94             ->push()
95             ->run();
96
97         if ($stable) {
98             $this->pharPublish();
99         }
100         $this->publish();
101
102         $this->taskGitStack()
103             ->tag($version)
104             ->push('origin master --tags')
105             ->run();
106
107         if ($stable) {
108             $version = $this->incrementVersion($version) . '-dev';
109             $this->writeVersion($version);
110
111             $this->taskGitStack()
112                 ->add('-A')
113                 ->commit("Prepare for $version")
114                 ->push()
115                 ->run();
116         }
117     }
118
119     /**
120      * Update changelog.
121      *
122      * Add an entry to the Robo CHANGELOG.md file.
123      *
124      * @param string $addition The text to add to the change log.
125      */
126     public function changed($addition)
127     {
128         $version = preg_replace('/-.*/', '', \Robo\Robo::VERSION);
129         return $this->taskChangelog()
130             ->version($version)
131             ->change($addition)
132             ->run();
133     }
134
135     /**
136      * Update the version of Robo.
137      *
138      * @param string $version The new verison for Robo.
139      *   Defaults to the next minor (bugfix) version after the current relelase.
140      * @option stage The version stage: dev, alpha, beta or rc. Use empty for stable.
141      */
142     public function versionBump($version = '', $options = ['stage' => ''])
143     {
144         // If the user did not specify a version, then update the current version.
145         if (empty($version)) {
146             $version = $this->incrementVersion(\Robo\Robo::VERSION, $options['stage']);
147         }
148         return $this->writeVersion($version);
149     }
150
151     /**
152      * Write the specified version string back into the Robo.php file.
153      * @param string $version
154      */
155     protected function writeVersion($version)
156     {
157         // Write the result to a file.
158         return $this->taskReplaceInFile(__DIR__.'/src/Robo.php')
159             ->regex("#VERSION = '[^']*'#")
160             ->to("VERSION = '".$version."'")
161             ->run();
162     }
163
164     /**
165      * Advance to the next SemVer version.
166      *
167      * The behavior depends on the parameter $stage.
168      *   - If $stage is empty, then the patch or minor version of $version is incremented
169      *   - If $stage matches the current stage in the current version, then add one
170      *     to the stage (e.g. alpha3 -> alpha4)
171      *   - If $stage does not match the current stage in the current version, then
172      *     reset to '1' (e.g. alpha4 -> beta1)
173      *
174      * @param string $version A SemVer version
175      * @param string $stage dev, alpha, beta, rc or an empty string for stable.
176      * @return string
177      */
178     protected function incrementVersion($version, $stage = '')
179     {
180         $stable = empty($stage);
181         $versionStageNumber = '0';
182         preg_match('/-([a-zA-Z]*)([0-9]*)/', $version, $match);
183         $match += ['', '', ''];
184         $versionStage = $match[1];
185         $versionStageNumber = $match[2];
186         if ($versionStage != $stage) {
187             $versionStageNumber = 0;
188         }
189         $version = preg_replace('/-.*/', '', $version);
190         $versionParts = explode('.', $version);
191         if ($stable) {
192             $versionParts[count($versionParts)-1]++;
193         }
194         $version = implode('.', $versionParts);
195         if (!$stable) {
196             $version .= '-' . $stage;
197             if ($stage != 'dev') {
198                 $versionStageNumber++;
199                 $version .= $versionStageNumber;
200             }
201         }
202         return $version;
203     }
204
205     /**
206      * Generate the Robo documentation files.
207      */
208     public function docs()
209     {
210         $collection = $this->collectionBuilder();
211         $collection->progressMessage('Generate documentation from source code.');
212         $files = Finder::create()->files()->name('*.php')->in('src/Task');
213         $docs = [];
214         foreach ($files as $file) {
215             if ($file->getFileName() == 'loadTasks.php') {
216                 continue;
217             }
218             if ($file->getFileName() == 'loadShortcuts.php') {
219                 continue;
220             }
221             $ns = $file->getRelativePath();
222             if (!$ns) {
223                 continue;
224             }
225             $class = basename(substr($file, 0, -4));
226             class_exists($class = "Robo\\Task\\$ns\\$class");
227             $docs[$ns][] = $class;
228         }
229         ksort($docs);
230
231         foreach ($docs as $ns => $tasks) {
232             $taskGenerator = $collection->taskGenDoc("docs/tasks/$ns.md");
233             $taskGenerator->filterClasses(function (\ReflectionClass $r) {
234                 return !($r->isAbstract() || $r->isTrait()) && $r->implementsInterface('Robo\Contract\TaskInterface');
235             })->prepend("# $ns Tasks");
236             sort($tasks);
237             foreach ($tasks as $class) {
238                 $taskGenerator->docClass($class);
239             }
240
241             $taskGenerator->filterMethods(
242                 function (\ReflectionMethod $m) {
243                     if ($m->isConstructor() || $m->isDestructor() || $m->isStatic()) {
244                         return false;
245                     }
246                     $undocumentedMethods =
247                     [
248                         '',
249                         'run',
250                         '__call',
251                         'inflect',
252                         'injectDependencies',
253                         'getCommand',
254                         'getPrinted',
255                         'getConfig',
256                         'setConfig',
257                         'logger',
258                         'setLogger',
259                         'setProgressIndicator',
260                         'progressIndicatorSteps',
261                         'setBuilder',
262                         'getBuilder',
263                         'collectionBuilder',
264                         'setVerbosityThreshold',
265                         'verbosityThreshold',
266                         'setOutputAdapter',
267                         'outputAdapter',
268                         'hasOutputAdapter',
269                         'verbosityMeetsThreshold',
270                         'writeMessage',
271                         'detectInteractive',
272                         'background',
273                         'timeout',
274                         'idleTimeout',
275                         'env',
276                         'envVars',
277                         'setInput',
278                         'interactive',
279                         'silent',
280                         'printed',
281                         'printOutput',
282                         'printMetadata',
283                     ];
284                     return !in_array($m->name, $undocumentedMethods) && $m->isPublic(); // methods are not documented
285                 }
286             )->processClassSignature(
287                 function ($c) {
288                     return "## " . preg_replace('~Task$~', '', $c->getShortName()) . "\n";
289                 }
290             )->processClassDocBlock(
291                 function (\ReflectionClass $c, $doc) {
292                     $doc = preg_replace('~@method .*?(.*?)\)~', '* `$1)` ', $doc);
293                     $doc = str_replace('\\'.$c->name, '', $doc);
294                     return $doc;
295                 }
296             )->processMethodSignature(
297                 function (\ReflectionMethod $m, $text) {
298                     return str_replace('#### *public* ', '* `', $text) . '`';
299                 }
300             )->processMethodDocBlock(
301                 function (\ReflectionMethod $m, $text) {
302
303                     return $text ? ' ' . trim(strtok($text, "\n"), "\n") : '';
304                 }
305             );
306         }
307         $collection->progressMessage('Documentation generation complete.');
308         return $collection->run();
309     }
310
311     /**
312      * Publish Robo.
313      *
314      * Builds a site in gh-pages branch. Uses mkdocs
315      */
316     public function publish()
317     {
318         $current_branch = exec('git rev-parse --abbrev-ref HEAD');
319
320         return $this->collectionBuilder()
321             ->taskGitStack()
322                 ->checkout('site')
323                 ->merge('master')
324             ->completion($this->taskGitStack()->checkout($current_branch))
325             ->taskFilesystemStack()
326                 ->copy('CHANGELOG.md', 'docs/changelog.md')
327             ->completion($this->taskFilesystemStack()->remove('docs/changelog.md'))
328             ->taskExec('mkdocs gh-deploy')
329             ->run();
330     }
331
332     /**
333      * Build the Robo phar executable.
334      */
335     public function pharBuild()
336     {
337         $this->checkPharReadonly();
338
339         // Create a collection builder to hold the temporary
340         // directory until the pack phar task runs.
341         $collection = $this->collectionBuilder();
342
343         $workDir = $collection->tmpDir();
344         $roboBuildDir = "$workDir/robo";
345
346         // Before we run `composer install`, we will remove the dev
347         // dependencies that we only use in the unit tests.  Any dev dependency
348         // that is in the 'suggested' section is used by a core task;
349         // we will include all of those in the phar.
350         $devProjectsToRemove = $this->devDependenciesToRemoveFromPhar();
351
352         // We need to create our work dir and run `composer install`
353         // before we prepare the pack phar task, so create a separate
354         // collection builder to do this step in.
355         $prepTasks = $this->collectionBuilder();
356
357         $preparationResult = $prepTasks
358             ->taskFilesystemStack()
359                 ->mkdir($workDir)
360             ->taskRsync()
361                 ->fromPath(
362                     [
363                         __DIR__ . '/composer.json',
364                         __DIR__ . '/scripts',
365                         __DIR__ . '/src',
366                         __DIR__ . '/data'
367                     ]
368                 )
369                 ->toPath($roboBuildDir)
370                 ->recursive()
371                 ->progress()
372                 ->stats()
373             ->taskComposerRemove()
374                 ->dir($roboBuildDir)
375                 ->dev()
376                 ->noUpdate()
377                 ->args($devProjectsToRemove)
378             ->taskComposerInstall()
379                 ->dir($roboBuildDir)
380                 ->noScripts()
381                 ->printed(true)
382                 ->run();
383
384         // Exit if the preparation step failed
385         if (!$preparationResult->wasSuccessful()) {
386             return $preparationResult;
387         }
388
389         // Decide which files we're going to pack
390         $files = Finder::create()->ignoreVCS(true)
391             ->files()
392             ->name('*.php')
393             ->name('*.exe') // for 1symfony/console/Resources/bin/hiddeninput.exe
394             ->name('GeneratedWrapper.tmpl')
395             ->path('src')
396             ->path('vendor')
397             ->notPath('docs')
398             ->notPath('/vendor\/.*\/[Tt]est/')
399             ->in(is_dir($roboBuildDir) ? $roboBuildDir : __DIR__);
400
401         // Build the phar
402         return $collection
403             ->taskPackPhar('robo.phar')
404                 ->addFiles($files)
405                 ->addFile('robo', 'robo')
406                 ->executable('robo')
407             ->taskFilesystemStack()
408                 ->chmod('robo.phar', 0777)
409             ->run();
410     }
411
412     protected function checkPharReadonly()
413     {
414         if (ini_get('phar.readonly')) {
415             throw new \Exception('Must set "phar.readonly = Off" in php.ini to build phars.');
416         }
417     }
418
419     /**
420      * The phar:build command removes the project requirements from the
421      * 'require-dev' section that are not in the 'suggest' section.
422      *
423      * @return array
424      */
425     protected function devDependenciesToRemoveFromPhar()
426     {
427         $composerInfo = (array) json_decode(file_get_contents(__DIR__ . '/composer.json'));
428
429         $devDependencies = array_keys((array)$composerInfo['require-dev']);
430         $suggestedProjects = array_keys((array)$composerInfo['suggest']);
431
432         return array_diff($devDependencies, $suggestedProjects);
433     }
434
435     /**
436      * Install Robo phar.
437      *
438      * Installs the Robo phar executable in /usr/bin. Uses 'sudo'.
439      */
440     public function pharInstall()
441     {
442         return $this->taskExec('sudo cp')
443             ->arg('robo.phar')
444             ->arg('/usr/bin/robo')
445             ->run();
446     }
447
448     /**
449      * Publish Robo phar.
450      *
451      * Commits the phar executable to Robo's GitHub pages site.
452      */
453     public function pharPublish()
454     {
455         $this->pharBuild();
456
457         $this->collectionBuilder()
458             ->taskFilesystemStack()
459                 ->rename('robo.phar', 'robo-release.phar')
460             ->taskGitStack()
461                 ->checkout('site')
462                 ->pull('origin site')
463             ->taskFilesystemStack()
464                 ->remove('robotheme/robo.phar')
465                 ->rename('robo-release.phar', 'robotheme/robo.phar')
466             ->taskGitStack()
467                 ->add('robotheme/robo.phar')
468                 ->commit('Update robo.phar to ' . \Robo\Robo::VERSION)
469                 ->push('origin site')
470                 ->checkout('master')
471                 ->run();
472     }
473 }