PageRenderTime 30ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Codeception/InitTemplate.php

http://github.com/Codeception/Codeception
PHP | 346 lines | 210 code | 32 blank | 104 comment | 9 complexity | e194513c2d585d6fb5768e2cfa88c918 MD5 | raw file
  1. <?php
  2. namespace Codeception;
  3. use Codeception\Command\Shared\FileSystem;
  4. use Codeception\Command\Shared\Style;
  5. use Codeception\Lib\ModuleContainer;
  6. use Symfony\Component\Console\Helper\QuestionHelper;
  7. use Symfony\Component\Console\Input\InputInterface;
  8. use Symfony\Component\Console\Output\OutputInterface;
  9. use Symfony\Component\Console\Question\ChoiceQuestion;
  10. use Symfony\Component\Console\Question\ConfirmationQuestion;
  11. use Symfony\Component\Console\Question\Question;
  12. /**
  13. * Codeception templates allow creating a customized setup and configuration for your project.
  14. * An abstract class for installation template. Each init template should extend it and implement a `setup` method.
  15. * Use it to build a custom setup class which can be started with `codecept init` command.
  16. *
  17. *
  18. * ```php
  19. * <?php
  20. * namespace Codeception\Template; // it is important to use this namespace so codecept init could locate this template
  21. * class CustomInstall extends \Codeception\InitTemplate
  22. * {
  23. * public function setup()
  24. * {
  25. * // implement this
  26. * }
  27. * }
  28. * ```
  29. * This class provides various helper methods for building customized setup
  30. */
  31. abstract class InitTemplate
  32. {
  33. use FileSystem;
  34. use Style;
  35. const GIT_IGNORE = '.gitignore';
  36. /**
  37. * @var string
  38. */
  39. protected $namespace = '';
  40. /**
  41. * @var string
  42. */
  43. protected $actorSuffix = 'Tester';
  44. /**
  45. * @var string
  46. */
  47. protected $workDir = '.';
  48. /**
  49. * @var InputInterface
  50. */
  51. protected $input;
  52. /**
  53. * @var OutputInterface
  54. */
  55. protected $output;
  56. public function __construct(InputInterface $input, OutputInterface $output)
  57. {
  58. $this->input = $input;
  59. $this->addStyles($output);
  60. $this->output = $output;
  61. }
  62. /**
  63. * Change the directory where Codeception should be installed.
  64. */
  65. public function initDir($workDir)
  66. {
  67. $this->checkInstalled($workDir);
  68. $this->sayInfo("Initializing Codeception in $workDir");
  69. $this->createDirectoryFor($workDir);
  70. chdir($workDir);
  71. $this->workDir = $workDir;
  72. }
  73. /**
  74. * Override this class to create customized setup.
  75. * @return mixed
  76. */
  77. abstract public function setup();
  78. /**
  79. * ```php
  80. * <?php
  81. * // propose firefox as default browser
  82. * $this->ask('select the browser of your choice', 'firefox');
  83. *
  84. * // propose firefox or chrome possible options
  85. * $this->ask('select the browser of your choice', ['firefox', 'chrome']);
  86. *
  87. * // ask true/false question
  88. * $this->ask('do you want to proceed (y/n)', true);
  89. * ```
  90. *
  91. * @param string $question
  92. * @param mixed $answer
  93. * @return mixed|string
  94. */
  95. protected function ask($question, $answer = null)
  96. {
  97. $question = "? $question";
  98. $dialog = new QuestionHelper();
  99. if (is_array($answer)) {
  100. $question .= " <info>(" . $answer[0] . ")</info> ";
  101. return $dialog->ask($this->input, $this->output, new ChoiceQuestion($question, $answer, 0));
  102. }
  103. if (is_bool($answer)) {
  104. $question .= " (y/n) ";
  105. return $dialog->ask($this->input, $this->output, new ConfirmationQuestion($question, $answer));
  106. }
  107. if ($answer) {
  108. $question .= " <info>($answer)</info>";
  109. }
  110. return $dialog->ask($this->input, $this->output, new Question("$question ", $answer));
  111. }
  112. /**
  113. * Print a message to console.
  114. *
  115. * ```php
  116. * <?php
  117. * $this->say('Welcome to Setup');
  118. * ```
  119. *
  120. *
  121. * @param string $message
  122. */
  123. protected function say($message = '')
  124. {
  125. $this->output->writeln($message);
  126. }
  127. /**
  128. * Print a successful message
  129. * @param string $message
  130. */
  131. protected function saySuccess($message)
  132. {
  133. $this->say("<notice> $message </notice>");
  134. }
  135. /**
  136. * Print error message
  137. * @param string $message
  138. */
  139. protected function sayError($message)
  140. {
  141. $this->say("<error> $message </error>");
  142. }
  143. /**
  144. * Print warning message
  145. * @param $message
  146. */
  147. protected function sayWarning($message)
  148. {
  149. $this->say("<warning> $message </warning>");
  150. }
  151. /**
  152. * Print info message
  153. * @param string $message
  154. */
  155. protected function sayInfo($message)
  156. {
  157. $this->say("<debug> $message</debug>");
  158. }
  159. /**
  160. * Create a helper class inside a directory
  161. *
  162. * @param $name
  163. * @param $directory
  164. */
  165. protected function createHelper($name, $directory)
  166. {
  167. $file = $this->createDirectoryFor(
  168. $dir = $directory . DIRECTORY_SEPARATOR . "Helper",
  169. "$name.php"
  170. ) . "$name.php";
  171. $gen = new Lib\Generator\Helper($name, $this->namespace);
  172. // generate helper
  173. $this->createFile(
  174. $file,
  175. $gen->produce()
  176. );
  177. require_once $file;
  178. $this->sayInfo("$name helper has been created in $dir");
  179. }
  180. /**
  181. * Create an empty directory and add a placeholder file into it
  182. * @param $dir
  183. */
  184. protected function createEmptyDirectory($dir)
  185. {
  186. $this->createDirectoryFor($dir);
  187. $this->createFile($dir . DIRECTORY_SEPARATOR . '.gitkeep', '');
  188. }
  189. protected function gitIgnore($path)
  190. {
  191. if (file_exists(self::GIT_IGNORE)) {
  192. file_put_contents($path . DIRECTORY_SEPARATOR . self::GIT_IGNORE, "*\n!" . self::GIT_IGNORE);
  193. }
  194. }
  195. protected function checkInstalled($dir = '.')
  196. {
  197. if (file_exists($dir . DIRECTORY_SEPARATOR . 'codeception.yml') || file_exists($dir . DIRECTORY_SEPARATOR . 'codeception.dist.yml')) {
  198. throw new \Exception("Codeception is already installed in this directory");
  199. }
  200. }
  201. /**
  202. * Create an Actor class and generate actions for it.
  203. * Requires a suite config as array in 3rd parameter.
  204. *
  205. * @param $name
  206. * @param $directory
  207. * @param $suiteConfig
  208. */
  209. protected function createActor($name, $directory, $suiteConfig)
  210. {
  211. $file = $this->createDirectoryFor(
  212. $directory,
  213. $name
  214. ) . $this->getShortClassName($name);
  215. $file .= '.php';
  216. $suiteConfig['namespace'] = $this->namespace;
  217. $config = Configuration::mergeConfigs(Configuration::$defaultSuiteSettings, $suiteConfig);
  218. $actorGenerator = new Lib\Generator\Actor($config);
  219. $content = $actorGenerator->produce();
  220. $this->createFile($file, $content);
  221. $this->sayInfo("$name actor has been created in $directory");
  222. $actionsGenerator = new Lib\Generator\Actions($config);
  223. $content = $actionsGenerator->produce();
  224. $generatedDir = $directory . DIRECTORY_SEPARATOR . '_generated';
  225. $this->createDirectoryFor($generatedDir, 'Actions.php');
  226. $this->createFile($generatedDir . DIRECTORY_SEPARATOR . $actorGenerator->getActorName() . 'Actions.php', $content);
  227. $this->sayInfo("Actions have been loaded");
  228. }
  229. protected function addModulesToComposer($modules)
  230. {
  231. $packages = ModuleContainer::$packages;
  232. $section = null;
  233. if (!file_exists('composer.json')) {
  234. $this->say('');
  235. $this->sayWarning('Can\'t locate composer.json, please add following packages into "require-dev" section of composer.json:');
  236. $this->say('');
  237. foreach (array_unique($modules) as $module) {
  238. if (!isset($packages[$module])) {
  239. continue;
  240. }
  241. $package = $packages[$module];
  242. $this->say(sprintf('"%s": "%s"', $package, "^1.0.0"));
  243. $composer[$section][$package] = "^1.0.0";
  244. }
  245. $this->say('');
  246. return;
  247. }
  248. $composer = json_decode(file_get_contents('composer.json'), true);
  249. if ($composer === null) {
  250. throw new \Exception("Invalid composer.json file. JSON can't be decoded");
  251. }
  252. $section = null;
  253. if (isset($composer['require'])) {
  254. if (isset($composer['require']['codeception/codeception'])) {
  255. $section = 'require';
  256. }
  257. }
  258. if (isset($composer['require-dev'])) {
  259. if (isset($composer['require-dev']['codeception/codeception'])) {
  260. $section = 'require-dev';
  261. }
  262. }
  263. if (!$section) {
  264. $section = 'require';
  265. }
  266. $packageCounter = 0;
  267. foreach (array_unique($modules) as $module) {
  268. if (!isset($packages[$module])) {
  269. continue;
  270. }
  271. $package = $packages[$module];
  272. if (isset($composer[$section][$package])) {
  273. continue;
  274. }
  275. $this->sayInfo("Adding $package for $module to composer.json");
  276. $composer[$section][$package] = "^1.0.0";
  277. $packageCounter++;
  278. }
  279. file_put_contents('composer.json', json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
  280. if ($packageCounter) {
  281. $this->say("$packageCounter new packages added to $section");
  282. }
  283. if ($packageCounter && $this->ask('composer.json updated. Do you want to run "composer update"?', true)) {
  284. $this->sayInfo('Running composer update');
  285. exec('composer update', $output, $status);
  286. if ($status !== 0) {
  287. $this->sayInfo('Composer installation failed. Please check composer.json and try to run "composer update" manually');
  288. return;
  289. }
  290. if (!empty($composer['config']['vendor_dir'])) {
  291. $this->updateComposerClassMap($composer['config']['vendor_dir']);
  292. } else {
  293. $this->updateComposerClassMap();
  294. }
  295. }
  296. return $packageCounter;
  297. }
  298. private function updateComposerClassMap($vendorDir = 'vendor')
  299. {
  300. $loader = require $vendorDir . '/autoload.php';
  301. $classMap = require $vendorDir . '/composer/autoload_classmap.php';
  302. $loader->addClassMap($classMap);
  303. $map = require $vendorDir . '/composer/autoload_psr4.php';
  304. foreach ($map as $namespace => $path) {
  305. $loader->setPsr4($namespace, $path);
  306. }
  307. }
  308. }