RoboFile.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  1. <?php
  2. require_once __DIR__.'/vendor/autoload.php';
  3. use Symfony\Component\Finder\Finder;
  4. use Robo\Task\Development\GenerateMarkdownDoc as Doc;
  5. class RoboFile extends \Robo\Tasks
  6. {
  7. const STABLE_BRANCH = '2.5';
  8. const REPO_BLOB_URL = 'https://github.com/Codeception/Codeception/blob';
  9. public function release()
  10. {
  11. $this->say("CODECEPTION RELEASE: ".\Codeception\Codecept::VERSION);
  12. $this->update();
  13. $this->buildDocs();
  14. $this->publishDocs();
  15. $this->buildPhar();
  16. $this->buildPhar5();
  17. $this->publishPhar();
  18. $this->publishGit();
  19. $this->publishBase(null, \Codeception\Codecept::VERSION);
  20. $this->versionBump();
  21. $this->update(); //update dependencies after release, because buildPhar5 set them to old versions
  22. }
  23. public function versionBump($version = '')
  24. {
  25. if (!$version) {
  26. $versionParts = explode('.', \Codeception\Codecept::VERSION);
  27. $versionParts[count($versionParts)-1]++;
  28. $version = implode('.', $versionParts);
  29. }
  30. $this->say("Bumping version to $version");
  31. $this->taskReplaceInFile('src/Codeception/Codecept.php')
  32. ->from(\Codeception\Codecept::VERSION)
  33. ->to($version)
  34. ->run();
  35. }
  36. public function update()
  37. {
  38. $this->clean();
  39. $this->taskComposerUpdate()->dir('tests/data/claypit')->run();
  40. $this->taskComposerUpdate()->run();
  41. }
  42. public function changed($change)
  43. {
  44. $this->taskChangelog()
  45. ->version(\Codeception\Codecept::VERSION)
  46. ->change($change)
  47. ->run();
  48. }
  49. protected function server()
  50. {
  51. $this->taskServer(8000)
  52. ->background()
  53. ->dir('tests/data/app')
  54. ->run();
  55. }
  56. public function testPhpbrowser($args = '', $opt = ['test|t' => null])
  57. {
  58. $test = $opt['test'] ? ':'.$opt['test'] : '';
  59. $this->server();
  60. $this->taskCodecept('./codecept')
  61. ->args($args)
  62. ->test('tests/unit/Codeception/Module/PhpBrowserTest.php'.$test)
  63. ->run();
  64. }
  65. public function testRestBrowser($args = '', $opt = ['test|t' => null])
  66. {
  67. $test = $opt['test'] ? ':'.$opt['test'] : '';
  68. $this->taskServer(8010)
  69. ->background()
  70. ->dir('tests/data')
  71. ->run();
  72. $this->taskCodecept('./codecept')
  73. ->test('tests/unit/Codeception/Module/PhpBrowserRestTest.php'.$test)
  74. ->args($args)
  75. ->run();
  76. }
  77. public function testCoverage()
  78. {
  79. $this->server();
  80. $this->taskSymfonyCommand(new \Codeception\Command\Run('run'))
  81. ->arg('suite', 'coverage')
  82. ->run();
  83. }
  84. public function testWebdriver($args = '', $opt = ['test|t' => null])
  85. {
  86. $test = $opt['test'] ? ':'.$opt['test'] : '';
  87. $this->taskServer(8000)
  88. ->dir('tests/data/app')
  89. ->background()
  90. ->host('0.0.0.0')
  91. ->run();
  92. $this->taskCodecept('./codecept')
  93. ->suite('web')
  94. ->args($args)
  95. ->run();
  96. }
  97. public function testLaunchServer()
  98. {
  99. $this->taskServer(8010)
  100. ->background()
  101. ->dir('tests/data/rest')
  102. ->run();
  103. $this->taskServer(8000)
  104. ->dir('tests/data/app')
  105. ->run();
  106. }
  107. public function testCli()
  108. {
  109. $this->taskSymfonyCommand(new \Codeception\Command\Run('run'))
  110. ->arg('suite', 'cli')
  111. ->run();
  112. $this->taskSymfonyCommand(new \Codeception\Command\Run('run'))
  113. ->arg('suite', 'tests/unit/Codeception/Command')
  114. ->run();
  115. }
  116. private function installDependenciesForPhp5()
  117. {
  118. $this->taskReplaceInFile('composer.json')
  119. ->regex('/"platform": \{.*?\}/')
  120. ->to('"platform": {"php": "5.6.0"}')
  121. ->run();
  122. $this->taskComposerUpdate()->run();
  123. }
  124. private function installDependenciesForPhp70()
  125. {
  126. $this->taskReplaceInFile('composer.json')
  127. ->regex('/"platform": \{.*?\}/')
  128. ->to('"platform": {"php": "7.0.0"}')
  129. ->run();
  130. $this->taskComposerUpdate()->run();
  131. }
  132. private function revertComposerJsonChanges()
  133. {
  134. $this->taskReplaceInFile('composer.json')
  135. ->regex('/"platform": \{.*?\}/')
  136. ->to('"platform": {}')
  137. ->run();
  138. }
  139. /**
  140. * @desc creates codecept.phar
  141. * @throws Exception
  142. */
  143. public function buildPhar()
  144. {
  145. $this->installDependenciesForPhp70();
  146. $this->packPhar('package/codecept.phar');
  147. $code = $this->taskExec('php codecept.phar')->dir('package')->run()->getExitCode();
  148. if ($code !== 0) {
  149. throw new Exception("There was problem compiling phar");
  150. }
  151. $this->revertComposerJsonChanges();
  152. }
  153. /**
  154. * @desc creates codecept.phar with Guzzle 5.3 and Symfony 2.8
  155. * @throws Exception
  156. */
  157. public function buildPhar5()
  158. {
  159. if (!file_exists('package/php54')) {
  160. mkdir('package/php54');
  161. }
  162. $this->installDependenciesForPhp5();
  163. $this->packPhar('package/codecept5.phar');
  164. $this->_copy('package/codecept5.phar', 'package/php54/codecept.phar');
  165. $code = $this->taskExec('php codecept.phar')->dir('package/php54')->run()->getExitCode();
  166. if ($code !== 0) {
  167. throw new Exception("There was problem compiling phar");
  168. }
  169. $this->revertComposerJsonChanges();
  170. }
  171. private function packPhar($pharFileName)
  172. {
  173. $pharTask = $this->taskPackPhar($pharFileName)
  174. ->compress()
  175. ->stub('package/stub.php');
  176. $finder = Finder::create()
  177. ->ignoreVCS(true)
  178. ->name('*.php')
  179. ->name('*.tpl.dist')
  180. ->name('*.html.dist')
  181. ->in('src');
  182. foreach ($finder as $file) {
  183. $pharTask->addFile('src/'.$file->getRelativePathname(), $file->getRealPath());
  184. }
  185. $finder = Finder::create()
  186. ->ignoreVCS(true)
  187. ->name('*.php')
  188. ->in('ext');
  189. foreach ($finder as $file) {
  190. $pharTask->addFile('ext/'.$file->getRelativePathname(), $file->getRealPath());
  191. }
  192. $finder = Finder::create()->files()
  193. ->ignoreVCS(true)
  194. ->name('*.php')
  195. ->name('*.css')
  196. ->name('*.png')
  197. ->name('*.js')
  198. ->name('*.css')
  199. ->name('*.eot')
  200. ->name('*.svg')
  201. ->name('*.ttf')
  202. ->name('*.wof')
  203. ->name('*.woff')
  204. ->name('*.woff2')
  205. ->name('*.png')
  206. ->name('*.tpl.dist')
  207. ->name('*.html.dist')
  208. ->exclude('videlalvaro')
  209. ->exclude('php-amqplib')
  210. ->exclude('pheanstalk')
  211. ->exclude('phpseclib')
  212. ->exclude('codegyre')
  213. ->exclude('monolog')
  214. ->exclude('phpspec')
  215. ->exclude('squizlabs')
  216. ->exclude('Tests')
  217. ->exclude('tests')
  218. ->exclude('benchmark')
  219. ->exclude('demo')
  220. ->in('vendor');
  221. foreach ($finder as $file) {
  222. $pharTask->addStripped('vendor/'.$file->getRelativePathname(), $file->getRealPath());
  223. }
  224. $pharTask->addFile('autoload.php', 'autoload.php')
  225. ->addFile('codecept', 'package/bin')
  226. ->addFile('shim.php', 'shim.php');
  227. if (file_exists(__DIR__ .'phpunit5-loggers.php')) {
  228. $pharTask->addFile('phpunit5-loggers.php', 'phpunit5-loggers.php');
  229. }
  230. $pharTask->run();
  231. }
  232. /**
  233. * @desc generates modules reference from source files
  234. */
  235. public function buildDocs()
  236. {
  237. $this->say('generating documentation from source files');
  238. $this->buildDocsModules();
  239. $this->buildDocsUtils();
  240. $this->buildDocsCommands();
  241. $this->buildDocsStub();
  242. $this->buildDocsApi();
  243. $this->buildDocsExtensions();
  244. }
  245. public function buildDocsModules()
  246. {
  247. $this->taskCleanDir('docs/modules')->run();
  248. $this->say("Modules");
  249. $modules = Finder::create()->files()->name('*.php')->in(__DIR__ . '/src/Codeception/Module');
  250. foreach ($modules as $module) {
  251. $moduleName = basename(substr($module, 0, -4));
  252. $className = 'Codeception\Module\\' . $moduleName;
  253. $source = "https://github.com/Codeception/Codeception/tree/"
  254. .self::STABLE_BRANCH."/src/Codeception/Module/$moduleName.php";
  255. $this->taskGenDoc('docs/modules/' . $moduleName . '.md')
  256. ->docClass($className)
  257. ->prepend('# '.$moduleName)
  258. ->append('<p>&nbsp;</p><div class="alert alert-warning">Module reference is taken from the source code. <a href="'.$source.'">Help us to improve documentation. Edit module reference</a></div>')
  259. ->processClassSignature(false)
  260. ->processClassDocBlock(function (\ReflectionClass $c, $text) {
  261. return "$text\n## Actions";
  262. })->processProperty(false)
  263. ->filterMethods(function (\ReflectionMethod $method) use ($className) {
  264. if ($method->isConstructor() or $method->isDestructor()) {
  265. return false;
  266. }
  267. if (!$method->isPublic()) {
  268. return false;
  269. }
  270. if (strpos($method->name, '_') === 0) {
  271. $doc = $method->getDocComment();
  272. try {
  273. $doc = $doc . $method->getPrototype()->getDocComment();
  274. } catch (\ReflectionException $e) {
  275. }
  276. if (strpos($doc, '@api') === false) {
  277. return false;
  278. }
  279. };
  280. return true;
  281. })->processMethod(function (\ReflectionMethod $method, $text) use ($className, $moduleName) {
  282. $title = "\n### {$method->name}\n";
  283. if (strpos($method->name, '_') === 0) {
  284. $text = str_replace("@api\n", '', $text);
  285. $text = "\n*hidden API method, expected to be used from Helper classes*\n" . $text;
  286. $text = str_replace("{{MODULE_NAME}}", $moduleName, $text);
  287. };
  288. if (!trim($text)) {
  289. return $title . "__not documented__\n";
  290. }
  291. $text = str_replace(
  292. ['@since', '@version'],
  293. [' * `Available since`', ' * `Available since`'],
  294. $text
  295. );
  296. $text = str_replace('@part ', ' * `[Part]` ', $text);
  297. $text = str_replace("@return mixed\n", '', $text);
  298. $text = preg_replace('~@return (.*?)~', ' * `return` $1', $text);
  299. $text = preg_replace("~^@(.*?)([$\s])~", ' * `$1` $2', $text);
  300. $result = $title . $text;
  301. return preg_replace('/\n(\s*\n){2,}/', "\n\n", $result);
  302. })->processMethodSignature(false)
  303. ->reorderMethods('ksort')
  304. ->run();
  305. }
  306. }
  307. public function buildDocsUtils()
  308. {
  309. $this->say("Util Classes");
  310. $utils = ['Autoload', 'Fixtures', 'Locator', 'XmlBuilder', 'JsonType', 'HttpCode'];
  311. foreach ($utils as $utilName) {
  312. $className = '\Codeception\Util\\' . $utilName;
  313. $source = self::REPO_BLOB_URL."/".self::STABLE_BRANCH."/src/Codeception/Util/$utilName.php";
  314. $this->documentApiClass('docs/reference/' . $utilName . '.md', $className, $source);
  315. }
  316. }
  317. public function buildDocsStub()
  318. {
  319. $this->say("Stub Classes");
  320. $this->taskGenDoc('docs/reference/Stub.md')
  321. ->docClass('Codeception\Stub')
  322. ->filterMethods(function(\ReflectionMethod $method) {
  323. if ($method->isConstructor() or $method->isDestructor()) return false;
  324. if (!$method->isPublic()) return false;
  325. if (strpos($method->name, '_') === 0) return false;
  326. return true;
  327. })
  328. ->processMethodDocBlock(
  329. function (\ReflectionMethod $m, $doc) {
  330. $doc = str_replace(array('@since'), array(' * available since version'), $doc);
  331. $doc = str_replace(array(' @', "\n@"), array(" * ", "\n * "), $doc);
  332. return $doc;
  333. })
  334. ->processProperty(false)
  335. ->run();
  336. $mocksDocumentation = <<<EOF
  337. # Mocks
  338. Declare mocks inside `Codeception\Test\Unit` class.
  339. If you want to use mocks outside it, check the reference for [Codeception/Stub](https://github.com/Codeception/Stub) library.
  340. EOF;
  341. $this->taskGenDoc('docs/reference/Mock.md')
  342. ->docClass('Codeception\Test\Feature\Stub')
  343. ->docClass('Codeception\Stub\Expected')
  344. ->processClassDocBlock(false)
  345. ->processClassSignature(false)
  346. ->prepend($mocksDocumentation)
  347. ->filterMethods(function(\ReflectionMethod $method) {
  348. if ($method->isConstructor() or $method->isDestructor()) return false;
  349. if (!$method->isPublic()) return false;
  350. if (strpos($method->name, '_') === 0) return false;
  351. if (strpos($method->name, 'stub') === 0) return false;
  352. return true;
  353. })
  354. ->run();
  355. }
  356. public function buildDocsApi()
  357. {
  358. $this->say("API Classes");
  359. $apiClasses = ['Codeception\Module', 'Codeception\InitTemplate'];
  360. foreach ($apiClasses as $apiClass) {
  361. $name = (new ReflectionClass($apiClass))->getShortName();
  362. $this->documentApiClass('docs/reference/' . $name . '.md', $apiClass, true);
  363. }
  364. }
  365. public function buildDocsCommands()
  366. {
  367. $this->say("Commands");
  368. $commands = Finder::create()->files()->name('*.php')->depth(0)->in(__DIR__ . '/src/Codeception/Command');
  369. $commandGenerator = $this->taskGenDoc('docs/reference/Commands.md');
  370. foreach ($commands as $command) {
  371. $commandName = basename(substr($command, 0, -4));
  372. $className = '\Codeception\Command\\' . $commandName;
  373. $commandGenerator->docClass($className);
  374. }
  375. $commandGenerator
  376. ->prepend("# Console Commands\n")
  377. ->processClassSignature(function ($r, $text) {
  378. return "## ".$r->getShortName();
  379. })
  380. ->filterMethods(function (ReflectionMethod $r) {
  381. return false;
  382. })
  383. ->run();
  384. }
  385. public function buildDocsExtensions()
  386. {
  387. $this->say('Extensions');
  388. $extensions = Finder::create()->files()->sortByName()->name('*.php')->in(__DIR__ . '/ext');
  389. $extGenerator= $this->taskGenDoc(__DIR__.'/ext/README.md');
  390. foreach ($extensions as $extension) {
  391. $extensionName = basename(substr($extension, 0, -4));
  392. $className = '\Codeception\Extension\\' . $extensionName;
  393. $extGenerator->docClass($className);
  394. }
  395. $extGenerator
  396. ->prepend("# Official Extensions\n")
  397. ->processClassSignature(function (ReflectionClass $r, $text) {
  398. $name = $r->getShortName();
  399. return "## $name\n\n[See Source](" . self::REPO_BLOB_URL."/".self::STABLE_BRANCH. "/ext/$name.php)";
  400. })
  401. ->filterMethods(function (ReflectionMethod $r) {
  402. return false;
  403. })
  404. ->filterProperties(function ($r) {
  405. return false;
  406. })
  407. ->run();
  408. }
  409. /**
  410. * @desc publishes generated phar to codeception.com
  411. */
  412. public function publishPhar()
  413. {
  414. $this->cloneSite();
  415. $version = \Codeception\Codecept::VERSION;
  416. if (strpos($version, self::STABLE_BRANCH) === 0) {
  417. $this->say("publishing to release branch");
  418. copy('../codecept.phar', 'codecept.phar');
  419. if (!is_dir('php54')) {
  420. mkdir('php54');
  421. }
  422. copy('../php54/codecept.phar', 'php5/codecept.phar');
  423. $this->taskExec('git add codecept.phar')->run();
  424. $this->taskExec('git add php5/codecept.phar')->run();
  425. }
  426. $this->taskFileSystemStack()
  427. ->mkdir("releases/$version")
  428. ->mkdir("releases/$version/php54")
  429. ->copy('../codecept.phar', "releases/$version/codecept.phar")
  430. ->copy('../php54/codecept.phar', "releases/$version/php54/codecept.phar")
  431. ->run();
  432. $this->taskGitStack()->add('-A')->run();
  433. $sortByVersion = function (\SplFileInfo $a, \SplFileInfo $b) {
  434. return version_compare($a->getBaseName(), $b->getBaseName());
  435. };
  436. $releases = array_reverse(
  437. iterator_to_array(Finder::create()->depth(0)->directories()->sort($sortByVersion)->in('releases'))
  438. );
  439. $branch = null;
  440. $releaseFile = $this->taskWriteToFile('builds.markdown')
  441. ->line('---')
  442. ->line('layout: page')
  443. ->line('title: Codeception Builds')
  444. ->line('---')
  445. ->line('');
  446. foreach ($releases as $release) {
  447. $releaseName = $release->getBasename();
  448. $downloadUrl = "http://codeception.com/releases/$releaseName/codecept.phar";
  449. list($major, $minor) = explode('.', $releaseName);
  450. if ("$major.$minor" != $branch) {
  451. $branch = "$major.$minor";
  452. $releaseFile->line("\n## $branch");
  453. if ($major < 2) {
  454. $releaseFile->line("*Requires: PHP 5.3 and higher + CURL*\n");
  455. } elseif ($major == 2 && $minor < 4) {
  456. $releaseFile->line("*Requires: PHP 5.4 and higher + CURL*\n");
  457. } else {
  458. $releaseFile->line("*Requires: PHP 5.6 and higher + CURL*\n");
  459. }
  460. $releaseFile->line("* **[Download Latest $branch Release]($downloadUrl)**");
  461. }
  462. $versionLine = "* [$releaseName]($downloadUrl)";
  463. if (file_exists("releases/$releaseName/php54/codecept.phar")) {
  464. $downloadUrl = "http://codeception.com/releases/$releaseName/php54/codecept.phar";
  465. if (version_compare($releaseName, '2.4.0', '>=')) {
  466. $versionLine .= ", [for PHP 5.6]($downloadUrl)";
  467. } elseif (version_compare($releaseName, '2.3.0', '>=')) {
  468. $versionLine .= ", [for PHP 5.4 - 5.6]($downloadUrl)";
  469. } else {
  470. $versionLine .= ", [for PHP 5.4 or 5.5]($downloadUrl)";
  471. }
  472. }
  473. $releaseFile->line($versionLine);
  474. }
  475. $releaseFile->run();
  476. $this->publishSite();
  477. }
  478. /**
  479. * Updates docs on codeception.com
  480. *
  481. */
  482. public function publishDocs()
  483. {
  484. if (strpos(\Codeception\Codecept::VERSION, self::STABLE_BRANCH) !== 0) {
  485. $this->say("The ".\Codeception\Codecept::VERSION." is not in release branch. Site is not build");
  486. return;
  487. }
  488. $this->say('building site...');
  489. $this->cloneSite();
  490. $this->taskCleanDir('docs')
  491. ->run();
  492. $this->taskFileSystemStack()
  493. ->mkdir('docs/reference')
  494. ->mkdir('docs/modules')
  495. ->run();
  496. chdir('../..');
  497. $this->say('building changelog');
  498. $this->taskWriteToFile('package/site/changelog.markdown')
  499. ->line('---')
  500. ->line('layout: page')
  501. ->line('title: Codeception Changelog')
  502. ->line('---')
  503. ->line('')
  504. ->line(
  505. '<div class="alert alert-warning">Download specific version at <a href="/builds">builds page</a></div>'
  506. )
  507. ->line('')
  508. ->line('# Changelog')
  509. ->line('')
  510. ->line($this->processChangelog())
  511. ->run();
  512. $docs = Finder::create()->files('*.md')->sortByName()->in('docs');
  513. $modules = [];
  514. $api = [];
  515. $reference = [];
  516. foreach ($docs as $doc) {
  517. $newfile = $doc->getFilename();
  518. $name = substr($doc->getBasename(), 0, -3);
  519. $contents = $doc->getContents();
  520. if (strpos($doc->getPathname(), 'docs'.DIRECTORY_SEPARATOR.'modules') !== false) {
  521. $newfile = 'docs/modules/' . $newfile;
  522. $modules[$name] = '/docs/modules/' . $doc->getBasename();
  523. $contents = str_replace('## ', '### ', $contents);
  524. $buttons = [
  525. 'source' => self::REPO_BLOB_URL."/".self::STABLE_BRANCH."/src/Codeception/Module/$name.php"
  526. ];
  527. // building version switcher
  528. foreach (['master', '2.3', '2.2', '2.1', '2.0', '1.8'] as $branch) {
  529. $buttons[$branch] = self::REPO_BLOB_URL."/$branch/docs/modules/$name.md";
  530. }
  531. $buttonHtml = "\n\n".'<div class="btn-group" role="group" style="float: right" aria-label="...">';
  532. foreach ($buttons as $link => $url) {
  533. if ($link == self::STABLE_BRANCH) {
  534. $link = "<strong>$link</strong>";
  535. }
  536. $buttonHtml.= '<a class="btn btn-default" href="'.$url.'">'.$link.'</a>';
  537. }
  538. $buttonHtml .= '</div>'."\n\n";
  539. $contents = $buttonHtml . $contents;
  540. } elseif (strpos($doc->getPathname(), 'docs'.DIRECTORY_SEPARATOR.'reference') !== false) {
  541. $newfile = 'docs/reference/' . $newfile;
  542. $reference[$name] = '/docs/reference/' . $doc->getBasename();
  543. } else {
  544. $newfile = 'docs/'.$newfile;
  545. $api[substr($name, 3)] = '/docs/'.$doc->getBasename();
  546. }
  547. copy($doc->getPathname(), 'package/site/' . $newfile);
  548. $highlight_languages = implode('|', ['php', 'html', 'bash', 'yaml', 'json', 'xml', 'sql', 'gherkin']);
  549. $contents = preg_replace(
  550. "~```\s?($highlight_languages)\b(.*?)```~ms",
  551. "{% highlight $1 %}\n$2\n{% endhighlight %}",
  552. $contents
  553. );
  554. $contents = str_replace('{% highlight %}', '{% highlight yaml %}', $contents);
  555. $contents = preg_replace("~```\s?(.*?)```~ms", "{% highlight yaml %}\n$1\n{% endhighlight %}", $contents);
  556. // set default language in order not to leave unparsed code inside '```'
  557. $matches = [];
  558. $title = $name;
  559. $contents = "---\nlayout: doc\ntitle: ".($title!="" ? $title." - " : "")
  560. ."Codeception - Documentation\n---\n\n".$contents;
  561. file_put_contents('package/site/' .$newfile, $contents);
  562. }
  563. chdir('package/site');
  564. $guides = array_keys($api);
  565. foreach ($api as $name => $url) {
  566. $filename = substr($url, 6);
  567. $doc = file_get_contents('docs/'.$filename)."\n\n\n";
  568. $i = array_search($name, $guides);
  569. if (isset($guides[$i+1])) {
  570. $next_title = $guides[$i+1];
  571. $next_url = $api[$guides[$i+1]];
  572. $next_url = substr($next_url, 0, -3);
  573. $doc .= "\n* **Next Chapter: [$next_title >]($next_url)**";
  574. }
  575. if (isset($guides[$i-1])) {
  576. $prev_title = $guides[$i-1];
  577. $prev_url = $api[$guides[$i-1]];
  578. $prev_url = substr($prev_url, 0, -3);
  579. $doc .= "\n* **Previous Chapter: [< $prev_title]($prev_url)**";
  580. }
  581. $this->taskWriteToFile('docs/'.$filename)
  582. ->text($doc)
  583. ->run();
  584. }
  585. $guides_list = '';
  586. foreach ($api as $name => $url) {
  587. $url = substr($url, 0, -3);
  588. $name = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\\1 \\2', $name);
  589. $name = preg_replace('/([a-z\d])([A-Z])/', '\\1 \\2', $name);
  590. $guides_list .= '<li><a href="'.$url.'">'.$name.'</a></li>';
  591. }
  592. file_put_contents('_includes/guides.html', $guides_list);
  593. $this->say("Building Guides index");
  594. $this->taskWriteToFile('_includes/guides.html')
  595. ->text($guides_list)
  596. ->run();
  597. $this->taskWriteToFile('docs/index.html')
  598. ->line('---')
  599. ->line('layout: doc')
  600. ->line('title: Codeception Documentation')
  601. ->line('---')
  602. ->line('')
  603. ->line("<h1>Codeception Documentation Guides</h1>")
  604. ->line('')
  605. ->text($guides_list)
  606. ->run();
  607. /**
  608. * Align modules in two columns like this:
  609. * A D
  610. * B E
  611. * C
  612. */
  613. $modules_cols = 2;
  614. $modules_rows = ceil(count($modules) / $modules_cols);
  615. $module_names_chunked = array_chunk(array_keys($modules), $modules_rows);
  616. $modules_list = '';
  617. for ($i = 0; $i < $modules_rows; $i++) {
  618. for ($j = 0; $j < $modules_cols; $j++) {
  619. if (isset($module_names_chunked[$j][$i])) {
  620. $name = $module_names_chunked[$j][$i];
  621. $url = substr($modules[$name], 0, -3);
  622. $modules_list .= '<li><a href="'.$url.'">'.$name.'</a></li>';
  623. }
  624. }
  625. }
  626. file_put_contents('_includes/modules.html', $modules_list);
  627. $reference_list = '';
  628. foreach ($reference as $name => $url) {
  629. if ($name == 'Commands') {
  630. continue;
  631. }
  632. if ($name == 'Configuration') {
  633. continue;
  634. }
  635. $url = substr($url, 0, -3);
  636. $reference_list .= '<li><a href="'.$url.'">'.$name.'</a></li>';
  637. }
  638. file_put_contents('_includes/reference.html', $reference_list);
  639. $this->say("Writing extensions docs");
  640. $this->taskWriteToFile('_includes/extensions.md')
  641. ->textFromFile(__DIR__.'/ext/README.md')
  642. ->run();
  643. $this->publishSite();
  644. $this->taskExec('git add')->args('.')->run();
  645. }
  646. /**
  647. * @desc creates a new version tag and pushes to github
  648. * @param null $branch
  649. * @param array $opt
  650. */
  651. public function publishGit($branch = null, $opt = ['tag|t' => null])
  652. {
  653. $version = isset($opt['tag']) ? $opt['tag'] : \Codeception\Codecept::VERSION;
  654. $this->say('creating new tag for '.$version);
  655. if (!$branch) {
  656. $branch = explode('.', $version);
  657. array_pop($branch);
  658. $branch = implode('.', $branch);
  659. }
  660. $this->taskExec("git tag $version")->run();
  661. $this->taskExec("git push origin $branch --tags")->run();
  662. }
  663. protected function processChangelog()
  664. {
  665. $sortByVersionDesc = function (\SplFileInfo $a, \SplFileInfo $b) {
  666. $pattern = '/^CHANGELOG-(\d+\.\d+).md$/';
  667. if (preg_match($pattern, $a->getBasename(), $matches1) && preg_match($pattern, $b->getBasename(), $matches2)) {
  668. return version_compare($matches1[1], $matches2[1]) * -1;
  669. }
  670. return 0;
  671. };
  672. $changelogFiles = Finder::create()->name('CHANGELOG-*.md')->in('.')->depth(0)->sort($sortByVersionDesc);
  673. $changelog = '';
  674. foreach ($changelogFiles as $file) {
  675. $changelog .= $file->getContents();
  676. }
  677. //user
  678. $changelog = preg_replace('~\s@([\w-]+)~', ' **[$1](https://github.com/$1)**', $changelog);
  679. //issue
  680. $changelog = preg_replace(
  681. '~#(\d+)~',
  682. '[#$1](https://github.com/Codeception/Codeception/issues/$1)',
  683. $changelog
  684. );
  685. //module
  686. $changelog = preg_replace('~\s\[(\w+)\]\s~', ' **[$1]** ', $changelog);
  687. return $changelog;
  688. }
  689. /**
  690. * @desc cleans all log and temp directories
  691. */
  692. public function clean()
  693. {
  694. $this->taskCleanDir([
  695. 'tests/log',
  696. 'tests/data/claypit/tests/_output',
  697. 'tests/data/included/_log',
  698. 'tests/data/included/jazz/tests/_log',
  699. 'tests/data/included/shire/tests/_log',
  700. ])->run();
  701. $this->taskDeleteDir([
  702. 'tests/data/claypit/c3tmp',
  703. 'tests/data/sandbox'
  704. ])->run();
  705. }
  706. public function buildActors()
  707. {
  708. $build = 'php codecept build';
  709. $this->taskExec($build)->run();
  710. $this->taskExec($build)->args('-c tests/data/claypit')->run();
  711. $this->taskExec($build)->args('-c tests/data/included')->run();
  712. $this->taskExec($build)->args('-c tests/data/included/jazz')->run();
  713. $this->taskExec($build)->args('-c tests/data/included/shire')->run();
  714. $this->taskExec($build)->args('-c tests/data/included/jazz')->run();
  715. }
  716. protected function cloneSite()
  717. {
  718. @mkdir("package/site");
  719. $this->taskExec('git clone')
  720. ->args('git@github.com:Codeception/codeception.github.com.git')
  721. ->args('package/site/')
  722. ->run();
  723. chdir('package/site');
  724. }
  725. protected function publishSite()
  726. {
  727. $this->taskGitStack()
  728. ->add('-A')
  729. ->commit('auto updated documentation')
  730. ->push()
  731. ->run();
  732. chdir('..');
  733. sleep(2);
  734. $this->taskDeleteDir('site')->run();
  735. chdir('..');
  736. $this->say("Site build succesfully");
  737. }
  738. /**
  739. * Publishes Codeception base
  740. * @param null $branch
  741. * @param null $tag
  742. */
  743. public function publishBase($branch = null, $tag = null)
  744. {
  745. if (!$branch) {
  746. $branch = self::STABLE_BRANCH;
  747. }
  748. $this->say("Updating Codeception Base distribution");
  749. $tempBranch = "tmp".uniqid();
  750. $this->taskGitStack()
  751. ->checkout("-b $tempBranch")
  752. ->run();
  753. $this->taskReplaceInFile('composer.json')
  754. ->from('"codeception/codeception"')
  755. ->to('"codeception/base"')
  756. ->run();
  757. $this->taskReplaceInFile('composer.json')
  758. ->regex('~^\s+"facebook\/webdriver".*$~m')
  759. ->to('')
  760. ->run();
  761. $this->taskReplaceInFile('composer.json')
  762. ->regex('~^\s+"guzzlehttp\/guzzle".*$~m')
  763. ->to('')
  764. ->run();
  765. $this->taskComposerUpdate()->run();
  766. $this->taskGitStack()
  767. ->add('composer.json')
  768. ->commit('auto-update')
  769. ->exec("push -f base $tempBranch:$branch")
  770. ->run();
  771. if ($tag) {
  772. $this->taskGitStack()
  773. ->exec("tag -d $tag")
  774. ->exec("push base :refs/tags/$tag")
  775. ->exec("tag $tag")
  776. ->push('base', $tag)
  777. ->run();
  778. }
  779. $this->taskGitStack()
  780. ->checkout('-- composer.json')
  781. ->checkout($branch)
  782. ->exec("branch -D $tempBranch")
  783. ->run();
  784. }
  785. /**
  786. * Checks Codeception code style
  787. * Most useful values for `report` option: `full`, `summary`, `diff`
  788. *
  789. * @param array $opt
  790. */
  791. public function codestyleCheck($opt = ['report|r' => 'summary'])
  792. {
  793. $this->say("Checking code style");
  794. $this->taskExec('php vendor/bin/phpcs')
  795. ->arg('.')
  796. ->arg('--standard=ruleset.xml')
  797. ->arg('--report=' . $opt['report'])
  798. ->arg('--ignore=tests,vendor,package,docs')
  799. ->run();
  800. }
  801. public function codestyleFix()
  802. {
  803. $this->taskExec('php vendor/bin/phpcbf')
  804. ->arg('.')
  805. ->arg('--standard=ruleset.xml')
  806. ->arg('--ignore=tests,vendor,package,docs')
  807. ->run();
  808. }
  809. /**
  810. * @param $file
  811. * @param $className
  812. * @param $source
  813. */
  814. protected function documentApiClass($file, $className, $all = false)
  815. {
  816. $uri = str_replace('\\', '/', $className);
  817. $source = self::REPO_BLOB_URL."/".self::STABLE_BRANCH."/src/$uri.php";
  818. $this->taskGenDoc($file)
  819. ->docClass($className)
  820. ->filterMethods(function (ReflectionMethod $r) use ($all, $className) {
  821. return $all || $r->isPublic();
  822. })
  823. ->append(
  824. '<p>&nbsp;</p><div class="alert alert-warning">Reference is taken from the source code. '
  825. . '<a href="' . $source . '">Help us to improve documentation. Edit module reference</a></div>'
  826. )
  827. ->processPropertySignature(function ($r) {
  828. return "\n#### $" . $r->name. "\n\n";
  829. })
  830. ->processPropertyDocBlock(function ($r, $text) {
  831. $modifiers = implode(' ', \Reflection::getModifierNames($r->getModifiers()));
  832. $text = ' *' . $modifiers . '* **$' . $r->name . "**\n" . $text;
  833. $text = preg_replace("~@(.*?)\s(.*)~", 'type `$2`', $text);
  834. return $text;
  835. })
  836. ->processClassDocBlock(
  837. function (ReflectionClass $r, $text) {
  838. return $text . "\n";
  839. }
  840. )
  841. ->processMethodSignature(function ($r, $text) {
  842. return "#### {$r->name}()\n\n" . ltrim($text, '#');
  843. })
  844. ->processMethodDocBlock(
  845. function (ReflectionMethod $r, $text) use ($file) {
  846. $file = str_replace(__DIR__, '', $r->getFileName());
  847. $source = self::REPO_BLOB_URL."/".self::STABLE_BRANCH. $file;
  848. $line = $r->getStartLine();
  849. $text = preg_replace("~^\s?@(.*?)\s~m", ' * `$1` $2', $text);
  850. $text .= "\n[See source]($source#L$line)";
  851. return "\n" . $text . "\n";
  852. }
  853. )
  854. ->reorderMethods('ksort')
  855. ->run();
  856. }
  857. }