3 namespace DrupalCodeGenerator\Command;
5 use DrupalCodeGenerator\ApplicationFactory;
6 use DrupalCodeGenerator\Asset;
7 use DrupalCodeGenerator\Utils;
8 use Symfony\Component\Console\Command\Command;
9 use Symfony\Component\Console\Input\InputInterface;
10 use Symfony\Component\Console\Input\InputOption;
11 use Symfony\Component\Console\Output\OutputInterface;
12 use Symfony\Component\Console\Question\Question;
15 * Base class for all generators.
17 abstract class BaseGenerator extends Command implements GeneratorInterface {
27 * The command description.
31 protected $description;
48 * A path where templates are stored.
52 protected $templatePath;
55 * The working directory.
66 protected $destination = 'modules/%';
71 * The key of the each item in the array is the path to the file and
72 * the value is the generated content of it.
76 * @deprecated Use self::$assets.
78 protected $files = [];
83 * @var \DrupalCodeGenerator\Asset[]
85 protected $assets = [];
88 * Twig template variables.
97 protected function configure() {
99 ->setName($this->name)
100 ->setDescription($this->description)
104 InputOption::VALUE_OPTIONAL,
110 InputOption::VALUE_OPTIONAL,
111 'Default JSON formatted answers'
115 $this->setAliases([$this->alias]);
118 if (!$this->templatePath) {
119 $this->templatePath = ApplicationFactory::getRoot() . '/templates';
126 protected function initialize(InputInterface $input, OutputInterface $output) {
127 $this->getHelperSet()->setCommand($this);
128 $this->getHelper('dcg_renderer')->addPath($this->templatePath);
130 $directory = $input->getOption('directory') ?: getcwd();
131 // No need to look up for extension root when generating an extension.
132 $extension_destinations = ['modules', 'profiles', 'themes'];
133 $is_extension = in_array($this->destination, $extension_destinations);
134 $this->directory = $is_extension
135 ? $directory : (Utils::getExtensionRoot($directory) ?: $directory);
137 // Display welcome message.
139 "\n Welcome to %s generator!",
142 $output->writeln($header);
143 $header_length = strlen(trim(strip_tags($header)));
144 $output->writeln('<fg=cyan;options=bold>–' . str_repeat('–', $header_length) . '–</>');
150 protected function execute(InputInterface $input, OutputInterface $output) {
152 // Render all assets.
153 $renderer = $this->getHelper('dcg_renderer');
154 foreach ($this->getAssets() as $asset) {
155 // Supply the asset with all collected variables if it has no local ones.
156 if (!$asset->getVars()) {
157 $asset->vars($this->vars);
159 $asset->render($renderer);
162 $dumped_files = $this->getHelper('dcg_dumper')->dump($input, $output);
163 $this->getHelper('dcg_output_handler')->printSummary($output, $dumped_files);
170 public function getLabel() {
175 * Returns list of rendered files.
178 * An associative array where each key is path to a file and value is
183 public function getFiles() {
190 public function getAssets() {
192 // Convert files into assets for legacy commands.
194 foreach ($this->getFiles() as $path => $file) {
195 $asset = new Asset();
197 if (!is_array($file)) {
198 $file = ['content' => $file];
200 if (isset($file['content'])) {
201 $asset->content($file['content']);
204 $asset->type('directory');
206 if (isset($file['action'])) {
207 $asset->action($file['action']);
209 if (isset($file['header_size'])) {
210 $asset->headerSize($file['header_size']);
212 if (isset($file['mode'])) {
213 $asset->mode($file['mode']);
217 return array_merge($assets, $this->assets);
220 return $this->assets;
226 public function setDirectory($directory) {
227 $this->directory = $directory;
233 public function getDirectory() {
234 return $this->directory;
240 public function setDestination($destination) {
241 $this->destination = $destination;
247 public function getDestination() {
248 return $this->destination;
252 * Renders a template.
254 * @param string $template
257 * Template variables.
260 * A string representing the rendered output.
262 protected function render($template, array $vars) {
263 return $this->getHelper('dcg_renderer')->render($template, $vars);
267 * Asks the user for template variables.
269 * @param \Symfony\Component\Console\Input\InputInterface $input
271 * @param \Symfony\Component\Console\Output\OutputInterface $output
273 * @param array $questions
274 * List of questions that the user should answer.
276 * Array of predefined template variables.
279 * Template variables.
281 * @see \DrupalCodeGenerator\InputHandler::collectVars()
283 protected function &collectVars(InputInterface $input, OutputInterface $output, array $questions, array $vars = []) {
284 $this->vars += $this->getHelper('dcg_input_handler')->collectVars($input, $output, $questions, $vars);
289 * Asks the user a single question and returns the answer.
291 * @param \Symfony\Component\Console\Input\InputInterface $input
293 * @param \Symfony\Component\Console\Output\OutputInterface $output
295 * @param \Symfony\Component\Console\Question\Question $question
298 * Array of predefined template variables.
303 * @see \DrupalCodeGenerator\InputHandler::collectVars()
305 protected function ask(InputInterface $input, OutputInterface $output, Question $question, array $vars = []) {
306 $answers = $this->getHelper('dcg_input_handler')->collectVars($input, $output, ['key' => $question], $vars);
307 return $answers['key'];
313 * @param string $type
316 * @return \DrupalCodeGenerator\Asset
319 protected function addAsset($type) {
320 $asset = (new Asset())->type($type);
321 $this->assets[] = $asset;
326 * Creates file asset.
328 * @return \DrupalCodeGenerator\Asset
331 protected function addFile() {
332 return $this->addAsset('file');
336 * Creates directory asset.
338 * @return \DrupalCodeGenerator\Asset
341 protected function addDirectory() {
342 return $this->addAsset('directory');
346 * Creates service file asset.
348 * @return \DrupalCodeGenerator\Asset
351 protected function addServicesFile() {
352 return $this->addFile()
353 ->path('{machine_name}.services.yml')
359 * Creates file asset.
361 * @param string $path
363 * @param string $template
364 * Twig template to render.
368 * @deprecated Use self::addFile() or self::addDirectory().
370 protected function setFile($path = NULL, $template = NULL, array $vars = []) {
373 ->template($template)
378 * Creates service file asset.
380 * @param string $path
382 * @param string $template
383 * Twig template to render.
387 * @deprecated Use self::addServiceFile().
389 protected function setServicesFile($path, $template, array $vars) {
390 $this->addServicesFile()
392 ->template($template)
399 * @param \Symfony\Component\Console\Input\InputInterface $input
401 * @param \Symfony\Component\Console\Output\OutputInterface $output
405 * List of collected services.
407 protected function collectServices(InputInterface $input, OutputInterface $output) {
409 $service_definitions = self::getServiceDefinitions();
410 $service_ids = array_keys($service_definitions);
414 $question = new Question('Type the service name or use arrows up/down. Press enter to continue');
415 $question->setValidator([Utils::class, 'validateServiceName']);
416 $question->setAutocompleterValues($service_ids);
417 $service = $this->ask($input, $output, $question);
421 $services[] = $service;
424 $this->vars['services'] = [];
425 foreach (array_unique($services) as $service_id) {
426 if (isset($service_definitions[$service_id])) {
427 $definition = $service_definitions[$service_id];
430 // Build the definition if the service is unknown.
432 'type' => 'Drupal\example\ExampleInterface',
433 'name' => str_replace('.', '_', $service_id),
434 'description' => "The $service_id service.",
437 $type_parts = explode('\\', $definition['type']);
438 $definition['short_type'] = end($type_parts);
439 $this->vars['services'][$service_id] = $definition;
441 return $this->vars['services'];
445 * Gets service definitions.
448 * List of service definitions keyed by service ID.
450 protected static function getServiceDefinitions() {
451 $data_encoded = file_get_contents(ApplicationFactory::getRoot() . '/resources/service-definitions.json');
452 return json_decode($data_encoded, TRUE);