Completed
Push — master ( e37ae2...a63d79 )
by Paulo Rodrigues
06:49
created

SetupCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 9
c 0
b 0
f 0
rs 9.6666
ccs 6
cts 6
cp 1
cc 1
eloc 5
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Rj\FrontendBundle\Command;
4
5
use Rj\FrontendBundle\Command\Options\SimpleOptionHelper;
6
use Rj\FrontendBundle\Command\Options\ChoiceOptionHelper;
7
use Symfony\Component\Console\Command\Command;
8
use Symfony\Component\Console\Input\ArrayInput;
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\Templating\EngineInterface;
13
use Symfony\Component\Templating\PhpEngine;
14
use Symfony\Component\Templating\TemplateNameParser;
15
use Symfony\Component\Templating\Loader\FilesystemLoader;
16
17
class SetupCommand extends Command
18
{
19
    /**
20
     * @var EngineInterface
21
     */
22
    private $templating;
23
24
    /**
25
     * @var string|null
26
     */
27
    private $rootDir = null;
28
29
    /**
30
     * @param string|null $name
31
     */
32 15
    public function __construct($name = null)
33
    {
34 15
        parent::__construct($name);
35
36 15
        $this->templating = new PhpEngine(
37 15
            new TemplateNameParser(),
38 15
            new FilesystemLoader([__DIR__.'/../Resources/blueprints/%name%'])
39
        );
40 15
    }
41
42
    /**
43
     * @param string $path
44
     */
45 15
    public function setRootDir($path)
46
    {
47 15
        $this->rootDir = $path;
48 15
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 15
    protected function configure()
54
    {
55
        $this
56 15
            ->setName('rj_frontend:setup')
57 15
            ->setDescription('Generate the configuration for the asset pipeline')
58 15
            ->addOption(
59 15
                'dry-run',
60 15
                null,
61 15
                InputOption::VALUE_NONE,
62 15
                'Output which commands would have been run instead of running them'
63
            )
64 15
            ->addOption(
65 15
                'force',
66 15
                null,
67 15
                InputOption::VALUE_NONE,
68 15
                'Force execution'
69
            )
70 15
            ->addOption(
71 15
                'src-dir',
72 15
                null,
73 15
                InputOption::VALUE_REQUIRED,
74 15
                'Path to the directory containing the source assets [e.g. '.$this->getDefaultOption('src-dir').']'
75
            )
76 15
            ->addOption(
77 15
                'dest-dir',
78 15
                null,
79 15
                InputOption::VALUE_REQUIRED,
80 15
                'Path to the directory containing the compiled assets [e.g. '.$this->getDefaultOption('dest-dir').']'
81
            )
82 15
            ->addOption(
83 15
                'pipeline',
84 15
                null,
85 15
                InputOption::VALUE_REQUIRED,
86 15
                'Asset pipeline to use [only gulp is available at the moment]'
87
            )
88 15
            ->addOption(
89 15
                'csspre',
90 15
                null,
91 15
                InputOption::VALUE_REQUIRED,
92 15
                'CSS preprocessor to use [sass, less or none]'
93
            )
94 15
            ->addOption(
95 15
                'coffee',
96 15
                null,
97 15
                InputOption::VALUE_REQUIRED,
98 15
                'Use the CoffeeScript compiler [true or false]'
99
            )
100
        ;
101 15
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 2
    protected function interact(InputInterface $input, OutputInterface $output)
107
    {
108 2
        $simpleOptionHelper = new SimpleOptionHelper($this, $input, $output);
109 2
        $choiceOptionHelper = new ChoiceOptionHelper($this, $input, $output);
110
111
        $simpleOptionHelper
112 2
            ->setDefaultValue($this->getDefaultOption('src-dir'))
113 2
            ->setOption(
114 2
                'src-dir',
115 2
                'Path to the directory containing the source assets [default is '.$this->getDefaultOption('src-dir').']'
116
            )
117
        ;
118
119
        $simpleOptionHelper
120 2
            ->setDefaultValue($this->getDefaultOption('dest-dir'))
121 2
            ->setOption(
122 2
                'dest-dir',
123 2
                'Path to the directory containing the compiled assets [default is '.$this->getDefaultOption('dest-dir').']'
124
            )
125
        ;
126
127
        $choiceOptionHelper
128 2
            ->setAllowedValues(['gulp'])
129 2
            ->setErrorMessage('%s is not a supported asset pipeline')
130 2
            ->setOption(
131 2
                'pipeline',
132 2
                'Asset pipeline to use [only gulp is available at the moment]'
133
            )
134
        ;
135
136
        $choiceOptionHelper
137 2
            ->setAllowedValues(['sass', 'less', 'none'])
138 2
            ->setErrorMessage('%s is not a supported CSS preprocessor')
139 2
            ->setOption(
140 2
                'csspre',
141 2
                'CSS preprocessor to use [default is '.$this->getDefaultOption('csspre').']'
142
            )
143
        ;
144
145
        $choiceOptionHelper
146 2
            ->setAllowedValues(['false', 'true'])
147 2
            ->setErrorMessage('%s is not a supported value for --coffee. Use either true or false')
148 2
            ->setOption(
149 2
                'coffee',
150 2
                'Whether to use the CoffeeScript compiler [default is '.$this->getDefaultOption('coffee').']'
151
            )
152
        ;
153
154 2
        $output->writeln('');
155 2
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160 15
    protected function execute(InputInterface $input, OutputInterface $output)
161
    {
162 15
        $this->processOptions($input);
163
164 15
        $output->writeln('<info>Selected options are:</info>');
165 15
        $output->writeln('src-dir:  '.$input->getOption('src-dir'));
166 15
        $output->writeln('dest-dir: '.$input->getOption('dest-dir'));
167 15
        $output->writeln('pipeline: '.$input->getOption('pipeline'));
168 15
        $output->writeln('csspre:   '.$input->getOption('csspre'));
169 15
        $output->writeln('coffee:   '.($input->getOption('coffee') ? 'true' : 'false'));
170
171 15
        if (!preg_match('|web/.+|', $input->getOption('dest-dir'))) {
172 3
            throw new \InvalidArgumentException("'dest-dir' must be a directory under web/");
173
        }
174
175 12
        $output->writeln('');
176 12
        $this->createSourceTree($input, $output);
177 12
        $this->createBuildFile($input, $output);
178 12
        $this->createPackageJson($input, $output);
179 12
        $this->createBowerJson($input, $output);
180
181 12
        $output->writeln('');
182 12
        $this->runInstallCommand($input, $output);
183 12
    }
184
185
    /**
186
     * @param InputInterface  $input
187
     * @param OutputInterface $output
188
     */
189 12
    private function runInstallCommand(InputInterface $input, OutputInterface $output)
190
    {
191 12
        if ($input->getOption('dry-run')) {
192 6
            return $output->writeln('<info>Would have installed npm and bower dependencies</info>');
193
        }
194
195 6
        $this->getApplication()->find('rj_frontend:install')
196 6
            ->run(new ArrayInput(['command' => 'rj_frontend:install']), $output);
197 6
    }
198
199
    /**
200
     * @param InputInterface  $input
201
     * @param OutputInterface $output
202
     */
203 12
    private function createSourceTree(InputInterface $input, OutputInterface $output)
204
    {
205 12
        $blueprints = __DIR__.'/../Resources/blueprints';
206 12
        $dryRun = $input->getOption('dry-run');
207 12
        $base = $input->getOption('src-dir');
208
209 12
        $output->writeln($dryRun
210 6
            ? '<info>Would have created directory tree for source assets:</info>'
211 12
            : '<info>Creating directory tree for source assets:</info>'
212
        );
213
214 12
        $blueprintDir = "$blueprints/images";
215 12
        $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/images");
216
217 12
        $blueprintDir = "$blueprints/stylesheets/".$input->getOption('csspre');
218 12
        $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/stylesheets");
219
220 12
        $blueprintDir = "$blueprints/scripts/";
221 12
        $blueprintDir .= $input->getOption('coffee') ? 'coffee' : 'js';
222 12
        $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/scripts");
223
224 12
        $output->writeln('');
225 12
    }
226
227
    /**
228
     * @param InputInterface  $input
229
     * @param OutputInterface $output
230
     */
231 12 View Code Duplication
    private function createBuildFile(InputInterface $input, OutputInterface $output)
232
    {
233
        $files = [
234 12
            'gulp' => 'gulp/gulpfile.js',
235
        ];
236
237 12
        $this->createFileFromTemplate($input, $output, 'pipelines/'.$files[$input->getOption('pipeline')]);
238 12
    }
239
240
    /**
241
     * @param InputInterface  $input
242
     * @param OutputInterface $output
243
     */
244 12 View Code Duplication
    private function createPackageJson(InputInterface $input, OutputInterface $output)
245
    {
246
        $files = [
247 12
            'gulp' => 'gulp/package.json',
248
        ];
249
250 12
        $this->createFileFromTemplate($input, $output, 'pipelines/'.$files[$input->getOption('pipeline')]);
251 12
    }
252
253
    /**
254
     * @param InputInterface  $input
255
     * @param OutputInterface $output
256
     */
257 12
    private function createBowerJson(InputInterface $input, OutputInterface $output)
258
    {
259 12
        $this->createFileFromTemplate($input, $output, 'bower.json');
260 12
    }
261
262
    /**
263
     * @param InputInterface  $input
264
     * @param OutputInterface $output
265
     * @param string          $blueprintDir
266
     * @param string          $targetDir
267
     */
268 12
    private function createDirFromBlueprint(InputInterface $input, OutputInterface $output, $blueprintDir, $targetDir)
269
    {
270 12
        $dryRun = $input->getOption('dry-run');
271
272 12
        if (!$dryRun && !file_exists($targetDir)) {
273 6
            mkdir($targetDir, 0777, true);
274
        }
275
276 12
        foreach (preg_grep('/^\.?\w+/', scandir($blueprintDir)) as $entry) {
277 12
            $target = $entry;
278
279 12
            $isPhpTemplate = substr($entry, strrpos($entry, '.')) === '.php';
280 12
            if ($isPhpTemplate) {
281 12
                $entry = str_replace('.php', '', $entry);
282 12
                $target = str_replace('.php', '', $target);
283
            }
284
285 12
            $entry = $blueprintDir.'/'.$entry;
286 12
            $target = $targetDir.'/'.$target;
287
288 12
            if (!$dryRun) {
289 6
                if ($isPhpTemplate) {
290 6
                    $this->renderTemplate($input, $output, $entry, $target);
291
                } else {
292 6
                    if (file_exists($target) && !$input->getOption('force')) {
293
                        $output->writeln(
294
                            "<error>$target already exists. Run this command with --force to overwrite</error>
295
                        ");
296
297
                        continue;
298
                    }
299
300 6
                    copy($entry, $target);
301
                }
302
            }
303
304 12
            $output->writeln($target);
305
        }
306 12
    }
307
308
    /**
309
     * @param InputInterface  $input
310
     * @param OutputInterface $output
311
     * @param string          $file
312
     */
313 12
    private function createFileFromTemplate(InputInterface $input, OutputInterface $output, $file)
314
    {
315 12
        $dryRun = $input->getOption('dry-run');
316
317 12
        $targetFile = basename($file);
318 12
        if (!empty($this->rootDir)) {
319 12
            $targetFile = $this->rootDir.'/'.$targetFile;
320
        }
321
322 12
        $output->writeln($dryRun
323 6
            ? "<info>Would have created file $targetFile</info>"
324 12
            : "<info>Creating file $targetFile</info>"
325
        );
326
327 12
        if ($dryRun) {
328 6
            return;
329
        }
330
331 6
        $this->renderTemplate($input, $output, $file, $targetFile);
332 6
    }
333
334
    /**
335
     * @param InputInterface  $input
336
     * @param OutputInterface $output
337
     * @param string          $file
338
     * @param string          $target
339
     */
340 6
    private function renderTemplate(InputInterface $input, OutputInterface $output, $file, $target)
341
    {
342 6
        if (file_exists($target) && !$input->getOption('force')) {
343 1
            $output->writeln(
344 1
                "<error>$target already exists. Run this command with --force to overwrite</error>"
345
            );
346
        }
347
348 6
        switch ($input->getOption('csspre')) {
349 6
            case 'sass':
350 5
                $stylesheetExtension = 'scss';
351 5
                break;
352 1
            case 'less':
353 1
                $stylesheetExtension = 'less';
354 1
                break;
355
            default:
356
                $stylesheetExtension = 'css';
357
                break;
358
        }
359
360 6
        file_put_contents($target, $this->templating->render("$file.php", [
361 6
            'projectName' => basename(getcwd()),
362 6
            'srcDir' => $input->getOption('src-dir'),
363 6
            'destDir' => $input->getOption('dest-dir'),
364 6
            'prefix' => str_replace('web/', '', $input->getOption('dest-dir')),
365 6
            'coffee' => $input->getOption('coffee'),
366 6
            'cssPre' => $input->getOption('csspre'),
367 6
            'stylesheetExtension' => $stylesheetExtension,
368
        ]));
369 6
    }
370
371
    /**
372
     * @param InputInterface $input
373
     */
374 15
    private function processOptions(InputInterface $input)
375
    {
376 15
        foreach ($input->getOptions() as $name => $value) {
377 15
            if (!$input->isInteractive() && $value === null) {
378 13
                $value = $this->getDefaultOption($name);
379
            }
380
381 15
            if ($value === 'true') {
382 2
                $value = true;
383 15
            } elseif ($value === 'false') {
384 12
                $value = false;
385
            }
386
387 15
            $input->setOption($name, $value);
388
        }
389 15
    }
390
391
    /**
392
     * @param string $name
393
     *
394
     * @return string
395
     */
396 15
    private function getDefaultOption($name)
397
    {
398
        $defaults = [
399 15
            'src-dir' => empty($this->rootDir) ? 'app/Resources' : $this->rootDir.'/app/Resources',
400 15
            'dest-dir' => empty($this->rootDir) ? 'web/assets' : $this->rootDir.'/web/assets',
401 15
            'pipeline' => 'gulp',
402 15
            'csspre' => 'sass',
403 15
            'coffee' => 'false',
404
        ];
405
406 15
        return $defaults[$name];
407
    }
408
}
409