5 * Contains \Drupal\Console\Core\Command\Chain\ChainCommand.
8 namespace Drupal\Console\Core\Command\Chain;
10 use Dflydev\PlaceholderResolver\DataSource\ArrayDataSource;
11 use Dflydev\PlaceholderResolver\RegexPlaceholderResolver;
12 use Symfony\Component\Console\Input\InputInterface;
13 use Symfony\Component\Console\Output\OutputInterface;
14 use Symfony\Component\Console\Input\ArrayInput;
15 use Symfony\Component\Console\Input\InputOption;
16 use Symfony\Component\Console\Command\Command;
17 use Symfony\Component\Filesystem\Filesystem;
18 use Symfony\Component\Yaml\Parser;
19 use Drupal\Console\Core\Utils\ChainQueue;
20 use Drupal\Console\Core\Utils\ChainDiscovery;
21 use Drupal\Console\Core\Command\Shared\InputTrait;
22 use Drupal\Console\Core\Style\DrupalStyle;
23 use Drupal\Console\Core\Command\Shared\CommandTrait;
28 * @package Drupal\Console\Core\Command\Chain
30 class ChainCommand extends Command
38 protected $chainQueue;
43 protected $chainDiscovery;
46 * ChainCommand constructor.
48 * @param ChainQueue $chainQueue
49 * @param ChainDiscovery $chainDiscovery
51 public function __construct(
52 ChainQueue $chainQueue,
53 ChainDiscovery $chainDiscovery
55 $this->chainQueue = $chainQueue;
56 $this->chainDiscovery = $chainDiscovery;
58 parent::__construct();
64 protected function configure()
68 ->setDescription($this->trans('commands.chain.description'))
72 InputOption::VALUE_OPTIONAL,
73 $this->trans('commands.chain.options.file')
78 InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
79 $this->trans('commands.chain.options.placeholder')
86 protected function interact(InputInterface $input, OutputInterface $output)
88 $io = new DrupalStyle($input, $output);
89 $file = $input->getOption('file');
92 $files = $this->chainDiscovery->getChainFiles(true);
95 $this->trans('commands.chain.questions.chain-file'),
100 $file = calculateRealPath($file);
101 $input->setOption('file', $file);
103 $chainContent = $this->getFileContents($file);
105 $placeholder = $input->getOption('placeholder');
107 $inlinePlaceHolders = $this->extractInlinePlaceHolders($chainContent);
109 if (!$placeholder && $inlinePlaceHolders) {
110 foreach ($inlinePlaceHolders as $key => $inlinePlaceHolder) {
111 $inlinePlaceHolderDefault = '';
112 if (strpos($inlinePlaceHolder, '|')>0) {
113 $placeholderParts = explode('|', $inlinePlaceHolder);
114 $inlinePlaceHolder = $placeholderParts[0];
115 $inlinePlaceHolderDefault = $placeholderParts[1];
116 $inlinePlaceHolders[$key] = $inlinePlaceHolder;
119 $placeholder[] = sprintf(
124 'Enter value for %s placeholder',
127 $inlinePlaceHolderDefault
131 $input->setOption('placeholder', $placeholder);
138 protected function execute(InputInterface $input, OutputInterface $output)
140 $io = new DrupalStyle($input, $output);
142 $interactive = false;
143 $learning = $input->hasOption('learning')?$input->getOption('learning'):false;
145 $file = $input->getOption('file');
147 $io->error($this->trans('commands.chain.messages.missing_file'));
152 $fileSystem = new Filesystem();
153 $file = calculateRealPath($file);
155 if (!$fileSystem->exists($file)) {
158 $this->trans('commands.chain.messages.invalid_file'),
166 $placeholder = $input->getOption('placeholder');
168 $placeholder = $this->inlineValueAsArray($placeholder);
170 $placeHolderOptions = [];
171 foreach ($placeholder as $placeholderItem) {
172 $placeHolderOptions[] = key($placeholderItem);
175 $chainContent = $this->getFileContents($file);
176 $inlinePlaceHolders = $this->extractInlinePlaceHolders($chainContent);
178 if ($inlinePlaceHolders) {
179 foreach ($inlinePlaceHolders as $key => $inlinePlaceHolder) {
180 if (!strpos($inlinePlaceHolder, '|')) {
184 $placeholderParts = explode('|', $inlinePlaceHolder);
185 $inlinePlaceHolder = $placeholderParts[0];
186 $inlinePlaceHolderDefault = $placeholderParts[1];
188 if (!$inlinePlaceHolderDefault) {
192 if (in_array($inlinePlaceHolder, $placeHolderOptions)) {
196 $placeholder[] = [$inlinePlaceHolder => $inlinePlaceHolderDefault];
200 $environmentPlaceHolders = $this->extractEnvironmentPlaceHolders($chainContent);
201 $envPlaceHolderMap = [];
202 $missingEnvironmentPlaceHolders = [];
203 foreach ($environmentPlaceHolders as $envPlaceHolder) {
204 if (!getenv($envPlaceHolder)) {
205 $missingEnvironmentPlaceHolders[$envPlaceHolder] = sprintf(
206 'export %s=%s_VALUE',
208 strtoupper($envPlaceHolder)
213 $envPlaceHolderMap[$envPlaceHolder] = getenv($envPlaceHolder);
216 if ($missingEnvironmentPlaceHolders) {
219 $this->trans('commands.chain.messages.missing-environment-placeholders'),
220 implode(', ', array_keys($missingEnvironmentPlaceHolders))
224 $io->info($this->trans('commands.chain.messages.set-environment-placeholders'));
225 $io->block(array_values($missingEnvironmentPlaceHolders));
230 $envPlaceHolderData = new ArrayDataSource($envPlaceHolderMap);
231 $placeholderResolver = new RegexPlaceholderResolver($envPlaceHolderData, '${{', '}}');
232 $chainContent = $placeholderResolver->resolvePlaceholder($chainContent);
234 $inlinePlaceHolders = $this->extractInlinePlaceHolders($chainContent);
236 $inlinePlaceHoldersReplacements = [];
237 foreach ($inlinePlaceHolders as $key => $inlinePlaceHolder) {
238 if (strpos($inlinePlaceHolder, '|') > 0) {
239 $placeholderParts = explode('|', $inlinePlaceHolder);
240 $inlinePlaceHoldersReplacements[] = $placeholderParts[0];
243 $inlinePlaceHoldersReplacements[] = $inlinePlaceHolder;
246 $chainContent = str_replace(
248 $inlinePlaceHoldersReplacements,
252 $inlinePlaceHolders = $inlinePlaceHoldersReplacements;
254 $inlinePlaceHolderMap = [];
255 foreach ($placeholder as $key => $placeholderItem) {
256 $inlinePlaceHolderMap = array_merge($inlinePlaceHolderMap, $placeholderItem);
259 $missingInlinePlaceHolders = [];
261 foreach ($inlinePlaceHolders as $inlinePlaceHolder) {
262 if (!array_key_exists($inlinePlaceHolder, $inlinePlaceHolderMap)) {
263 $missingInlinePlaceHolders[$inlinePlaceHolder] = sprintf(
264 '--placeholder="%s:%s_VALUE"',
266 strtoupper($inlinePlaceHolder)
271 if ($missingInlinePlaceHolders) {
274 $this->trans('commands.chain.messages.missing-inline-placeholders'),
275 implode(', ', array_keys($missingInlinePlaceHolders))
279 $io->info($this->trans('commands.chain.messages.set-inline-placeholders'));
280 $io->block(array_values($missingInlinePlaceHolders));
285 $inlinePlaceHolderData = new ArrayDataSource($inlinePlaceHolderMap);
286 $placeholderResolver = new RegexPlaceholderResolver($inlinePlaceHolderData, '%{{', '}}');
287 $chainContent = $placeholderResolver->resolvePlaceholder($chainContent);
289 $parser = new Parser();
290 $configData = $parser->parse($chainContent);
293 if (array_key_exists('commands', $configData)) {
294 $commands = $configData['commands'];
297 $chainInlineOptions = $input->getOptions();
298 unset($chainInlineOptions['file']);
300 foreach ($commands as $command) {
302 $arguments = !empty($command['arguments']) ? $command['arguments'] : [];
303 $options = !empty($command['options']) ? $command['options'] : [];
305 foreach ($arguments as $key => $value) {
306 $moduleInputs[$key] = is_null($value) ? '' : $value;
309 foreach ($options as $key => $value) {
310 $moduleInputs['--'.$key] = is_null($value) ? '' : $value;
313 // Get application global options
314 foreach ($this->getApplication()->getDefinition()->getOptions() as $option) {
315 $optionName = $option->getName();
316 if (array_key_exists($optionName, $chainInlineOptions)) {
317 $optionValue = $chainInlineOptions[$optionName];
318 // Set global option only if is not available in command options
319 if (!isset($moduleInputs['--' . $optionName]) && $optionValue) {
320 $moduleInputs['--' . $optionName] = $optionValue;
325 $application = $this->getApplication();
326 $callCommand = $application->find($command['command']);
332 $io->text($command['command']);
335 $input = new ArrayInput($moduleInputs);
336 if (!is_null($interactive)) {
337 $input->setInteractive($interactive);
340 $allowFailure = array_key_exists('allow_failure', $command)?$command['allow_failure']:false;
342 $callCommand->run($input, $io);
343 } catch (\Exception $e) {
344 if (!$allowFailure) {
345 $io->error($e->getMessage());
355 * Helper to load and clean up the chain file.
357 * @param string $file The file name
359 * @return string $contents The contents of the file
361 private function getFileContents($file)
363 $contents = file_get_contents($file);
365 // Remove lines with comments.
366 $contents = preg_replace('![ \t]*#.*[ \t]*[\r|\r\n|\n]!', PHP_EOL, $contents);
368 $contents = preg_replace("/(^[\r\n]*|[\r\n]+)[\t]*[\r\n]+/", PHP_EOL, $contents);
373 private function extractPlaceHolders($chainContent, $identifier)
376 $regex = '/\\'.$identifier.'{{(.*?)}}/';
383 if (!$placeHolders) {
387 return array_unique($placeHolders[1]);
390 private function extractInlinePlaceHolders($chainContent)
392 return $this->extractPlaceHolders($chainContent, '%');
395 private function extractEnvironmentPlaceHolders($chainContent)
397 return $this->extractPlaceHolders($chainContent, '$');