4 * Contains Drupal\Console\Core\Utils\Site.
7 namespace Drupal\Console\Core\Utils;
9 use Symfony\Component\Finder\Finder;
10 use Symfony\Component\Yaml\Yaml;
13 * Class ChainDiscovery
15 * @package Drupal\Console\Core\Utils
25 * @var ConfigurationManager
27 protected $configurationManager;
32 protected $messageManager;
35 * @var TranslatorManagerInterface
37 protected $translatorManager;
42 private $directories = [];
52 private $filesPerDirectory = [];
54 const INLINE_REGEX = '/{{(.*?)}}/';
55 const ENV_REGEX = '/%env\((.*?)\)%/';
60 private $inlineRegexLegacy = [
68 private $envRegexLegacy = [
76 * ChainDiscovery constructor.
78 * @param string $appRoot
79 * @param ConfigurationManager $configurationManager
80 * @param MessageManager $messageManager
81 * @param TranslatorManagerInterface $translatorManager
83 public function __construct(
85 ConfigurationManager $configurationManager,
86 MessageManager $messageManager,
87 TranslatorManagerInterface $translatorManager
89 $this->appRoot = $appRoot;
90 $this->configurationManager = $configurationManager;
91 $this->messageManager = $messageManager;
92 $this->translatorManager = $translatorManager;
94 $directories = array_map(
96 return $item . 'chain/';
98 $configurationManager->getConfigurationDirectories(true)
101 $this->addDirectories($directories);
105 * @param array $directories
107 public function addDirectories(array $directories)
109 $this->directories = array_merge($this->directories, $directories);
117 public function getChainFiles()
119 return $this->getFiles();
125 public function getFiles()
131 foreach ($this->directories as $directory) {
132 if (!is_dir($directory)) {
135 $finder = new Finder();
139 foreach ($finder as $file) {
141 $filePath = $file->getRealPath();
142 if (empty($filePath)) {
143 $filePath = $directory . $file->getBasename();
146 if (!is_file($filePath)) {
149 $this->files[$filePath] = [
150 'directory' => $directory,
151 'file_name' => $file->getBasename(),
155 $this->getFileContents($filePath);
156 $this->getFileMetadata($filePath);
158 if ($this->files[$filePath]['messages']) {
159 $this->messageManager->comment(
164 $this->messageManager->listing(
165 $this->files[$filePath]['messages'],
171 $this->filesPerDirectory[$directory][] = $file->getBasename();
181 public function getFilesPerDirectory()
183 return $this->filesPerDirectory;
189 public function getChainCommands()
192 $files = array_keys($this->getFiles());
193 foreach ($files as $file) {
194 $chainMetadata = $this->getFileMetadata($file);
196 if (!$chainMetadata) {
200 $name = $chainMetadata['command']['name'];
201 $description = $chainMetadata['command']['description'];
203 $chainCommands[$name] = [
204 'description' => $description,
208 $this->files[$file]['command'] = $name;
209 $this->files[$file]['description'] = $description;
212 return $chainCommands;
215 public function parseContent($file, $placeholders)
217 $placeholders = array_filter(
219 function ($element) {
220 return $element !== null;
224 unset($placeholders['file']);
225 unset($placeholders['placeholder']);
227 $contents = $this->getFileContents($file);
229 $loader = new \Twig_Loader_Array(
231 'chain' => $contents,
235 $twig = new \Twig_Environment($loader);
236 $envFunction = new \Twig_SimpleFunction(
238 function ($variableName) {
239 $variableValue = getenv($variableName);
240 if (!empty($variableValue)) {
241 return $variableValue;
244 return '%env('.$variableName.')%';
247 $twig->addFunction($envFunction);
249 $variables = $this->extractInlinePlaceHolderNames($contents);
251 foreach ($variables as $variable) {
252 if (!array_key_exists($variable, $placeholders)) {
253 $placeholders[$variable] = '{{ ' . $variable . ' }}';
257 return $twig->render('chain', $placeholders);
260 public function getFileMetadata($file)
262 if ($metadata = $this->getCacheMetadata($file)) {
266 $contents = $this->getFileContents($file);
268 $line = strtok($contents, PHP_EOL);
270 while ($line !== false) {
273 if ($index === 1 && $line !== 'command:') {
277 if ($index > 1 && substr($line, 0, 2) !== " ") {
281 $metadata .= $line . PHP_EOL;
282 $line = strtok(PHP_EOL);
285 $chainMetadata = $this->processMetadata($metadata);
287 if (!$chainMetadata) {
288 $this->files[$file]['messages'][] = $this->translatorManager
289 ->trans('commands.chain.messages.metadata-registration');
293 $this->files[$file]['metadata'] = $chainMetadata;
295 return $chainMetadata;
298 private function processMetadata($metadata) {
303 $chainMetadata = Yaml::parse($metadata);
305 if (!$chainMetadata || !is_array($chainMetadata)) {
309 if (!array_key_exists('command', $chainMetadata) || !is_array($chainMetadata['command'])) {
313 if (!array_key_exists('name', $chainMetadata['command'])) {
317 if (!array_key_exists('description', $chainMetadata['command'])) {
318 $chainMetadata['command']['description'] = '';
321 return $chainMetadata;
325 * Helper to load and clean up the chain file.
327 * @param string $file The file name
329 * @return string $contents The contents of the file
331 public function getFileContents($file)
337 if ($contents = $this->getCacheContent($file)) {
341 $contents = file_get_contents($file);
343 // Support BC for legacy inline variables.
344 $inlineLegacyContent = preg_replace(
345 $this->inlineRegexLegacy,
350 if ($contents !== $inlineLegacyContent) {
351 $this->files[$file]['messages'][] = $this->translatorManager
352 ->trans('commands.chain.messages.legacy-inline');
353 $contents = $inlineLegacyContent;
356 // Support BC for legacy environment variables.
357 $envLegacyContent = preg_replace(
358 $this->envRegexLegacy,
363 if ($contents !== $envLegacyContent) {
364 $this->files[$file]['messages'][] = $this->translatorManager
365 ->trans('commands.chain.messages.legacy-environment');
366 $contents = $envLegacyContent;
369 // Remove lines with comments.
370 $contents = preg_replace(
371 '![ \t]*#.*[ \t]*[\r|\r\n|\n]!',
377 $contents = preg_replace(
378 "/(^[\r\n]*|[\r\n]+)[\t]*[\r\n]+/",
383 $this->files[$file]['content'] = $contents;
388 private function getCacheContent($file)
390 if (!array_key_exists($file, $this->files)) {
394 if (!array_key_exists('content', $this->files[$file])) {
398 return $this->files[$file]['content'];
401 private function getCacheMetadata($file)
403 if (!array_key_exists($file, $this->files)) {
407 if (!array_key_exists('metadata', $this->files[$file])) {
411 return $this->files[$file]['metadata'];
414 private function extractPlaceHolders(
418 $placeHoldersExtracted = [];
422 $placeHoldersExtracted
425 if (!$placeHoldersExtracted) {
429 return array_unique($placeHoldersExtracted[1]);
432 public function extractInlinePlaceHolderNames($content)
434 preg_match_all($this::INLINE_REGEX, $content, $matches);
444 public function extractInlinePlaceHolders($chainContent)
446 $extractedInlinePlaceHolders = $this->extractPlaceHolders(
450 $extractedVars = $this->extractVars($chainContent);
452 $inlinePlaceHolders = [];
453 foreach ($extractedInlinePlaceHolders as $key => $inlinePlaceHolder) {
454 $inlinePlaceHolder = trim($inlinePlaceHolder);
455 $placeholderValue = null;
456 if (array_key_exists($inlinePlaceHolder, $extractedVars)) {
457 $placeholderValue = $extractedVars[$inlinePlaceHolder];
459 $inlinePlaceHolders[$inlinePlaceHolder] = $placeholderValue;
462 return $inlinePlaceHolders;
465 public function extractEnvironmentPlaceHolders($chainContent)
467 return $this->extractPlaceHolders($chainContent, $this::ENV_REGEX);
470 public function extractVars($chainContent)
472 $chain = Yaml::parse($chainContent);
473 if (!array_key_exists('vars', $chain)) {
477 return $chain['vars'];