Completed
Push — master ( 9c1f83...0c1135 )
by Paulo Rodrigues
10:00
created

SetupCommand   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 325
Duplicated Lines 4.92 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 95.54%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 42
c 1
b 0
f 1
lcom 1
cbo 9
dl 16
loc 325
ccs 193
cts 202
cp 0.9554
rs 8.295

15 Methods

Rating   Name   Duplication   Size   Complexity  
A setRootDir() 0 4 1
A createBowerJson() 0 4 1
A createFileFromTemplate() 0 20 4
A __construct() 0 9 1
A configure() 0 49 1
A interact() 0 50 1
B execute() 0 24 3
A runInstallCommand() 0 9 2
A createSourceTree() 0 23 3
A createBuildFile() 8 8 1
A createPackageJson() 8 8 1
D createDirFromBlueprint() 0 39 9
B renderTemplate() 0 30 5
B processOptions() 0 16 6
A getDefaultOption() 0 12 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SetupCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SetupCommand, and based on these observations, apply Extract Interface, too.

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\PhpEngine;
13
use Symfony\Component\Templating\TemplateNameParser;
14
use Symfony\Component\Templating\Loader\FilesystemLoader;
15
16
class SetupCommand extends Command
17
{
18
    private $templating;
19
    private $rootDir = null;
20
21 30
    public function __construct($name = null)
22
    {
23 30
        parent::__construct($name);
24
25 30
        $this->templating = new PhpEngine(
26 30
            new TemplateNameParser(),
27 30
            new FilesystemLoader(array(__DIR__.'/../Resources/blueprints/%name%'))
28
        );
29 30
    }
30
31 30
    public function setRootDir($path)
32
    {
33 30
        $this->rootDir = $path;
34 30
    }
35
36 30
    protected function configure()
37
    {
38
        $this
39 30
            ->setName('rj_frontend:setup')
40 30
            ->setDescription('Generate the configuration for the asset pipeline')
41 30
            ->addOption(
42 30
                'dry-run',
43 30
                null,
44 30
                InputOption::VALUE_NONE,
45 30
                'Output which commands would have been run instead of running them'
46
            )
47 30
            ->addOption(
48 30
                'force',
49 30
                null,
50 30
                InputOption::VALUE_NONE,
51 30
                'Force execution'
52
            )
53 30
            ->addOption(
54 30
                'src-dir',
55 30
                null,
56 30
                InputOption::VALUE_REQUIRED,
57 30
                'Path to the directory containing the source assets [e.g. '.$this->getDefaultOption('src-dir').']'
58
            )
59 30
            ->addOption(
60 30
                'dest-dir',
61 30
                null,
62 30
                InputOption::VALUE_REQUIRED,
63 30
                'Path to the directory containing the compiled assets [e.g. '.$this->getDefaultOption('dest-dir').']'
64
            )
65 30
            ->addOption(
66 30
                'pipeline',
67 30
                null,
68 30
                InputOption::VALUE_REQUIRED,
69 30
                'Asset pipeline to use [only gulp is available at the moment]'
70
            )
71 30
            ->addOption(
72 30
                'csspre',
73 30
                null,
74 30
                InputOption::VALUE_REQUIRED,
75 30
                'CSS preprocessor to use [sass, less or none]'
76
            )
77 30
            ->addOption(
78 30
                'coffee',
79 30
                null,
80 30
                InputOption::VALUE_REQUIRED,
81 30
                'Use the CoffeeScript compiler [true or false]'
82
            )
83
        ;
84 30
    }
85
86 4
    protected function interact(InputInterface $input, OutputInterface $output)
87
    {
88 4
        $simpleOptionHelper = new SimpleOptionHelper($this, $input, $output);
89 4
        $choiceOptionHelper = new ChoiceOptionHelper($this, $input, $output);
90
91
        $simpleOptionHelper
92 4
            ->setDefaultValue($this->getDefaultOption('src-dir'))
93 4
            ->setOption(
94 4
                'src-dir',
95 4
                'Path to the directory containing the source assets [default is '.$this->getDefaultOption('src-dir').']'
96
            )
97
        ;
98
99
        $simpleOptionHelper
100 4
            ->setDefaultValue($this->getDefaultOption('dest-dir'))
101 4
            ->setOption(
102 4
                'dest-dir',
103 4
                'Path to the directory containing the compiled assets [default is '.$this->getDefaultOption('dest-dir').']'
104
            )
105
        ;
106
107
        $choiceOptionHelper
108 4
            ->setAllowedValues(array('gulp'))
109 4
            ->setErrorMessage('%s is not a supported asset pipeline')
110 4
            ->setOption(
111 4
                'pipeline',
112 4
                'Asset pipeline to use [only gulp is available at the moment]'
113
            )
114
        ;
115
116
        $choiceOptionHelper
117 4
            ->setAllowedValues(array('sass', 'less', 'none'))
118 4
            ->setErrorMessage('%s is not a supported CSS preprocessor')
119 4
            ->setOption(
120 4
                'csspre',
121 4
                'CSS preprocessor to use [default is '.$this->getDefaultOption('csspre').']'
122
            )
123
        ;
124
125
        $choiceOptionHelper
126 4
            ->setAllowedValues(array('false', 'true'))
127 4
            ->setErrorMessage('%s is not a supported value for --coffee. Use either true or false')
128 4
            ->setOption(
129 4
                'coffee',
130 4
                'Whether to use the CoffeeScript compiler [default is '.$this->getDefaultOption('coffee').']'
131
            )
132
        ;
133
134 4
        $output->writeln('');
135 4
    }
136
137 30
    protected function execute(InputInterface $input, OutputInterface $output)
138
    {
139 30
        $this->processOptions($input);
140
141 30
        $output->writeln('<info>Selected options are:</info>');
142 30
        $output->writeln('src-dir:  '.$input->getOption('src-dir'));
143 30
        $output->writeln('dest-dir: '.$input->getOption('dest-dir'));
144 30
        $output->writeln('pipeline: '.$input->getOption('pipeline'));
145 30
        $output->writeln('csspre:   '.$input->getOption('csspre'));
146 30
        $output->writeln('coffee:   '.($input->getOption('coffee') ? 'true' : 'false'));
147
148 30
        if (!preg_match('|web/.+|', $input->getOption('dest-dir'))) {
149 6
            throw new \InvalidArgumentException("'dest-dir' must be a directory under web/");
150
        }
151
152 24
        $output->writeln('');
153 24
        $this->createSourceTree($input, $output);
154 24
        $this->createBuildFile($input, $output);
155 24
        $this->createPackageJson($input, $output);
156 24
        $this->createBowerJson($input, $output);
157
158 24
        $output->writeln('');
159 24
        $this->runInstallCommand($input, $output);
160 24
    }
161
162 24
    private function runInstallCommand($input, $output)
163
    {
164 24
        if ($input->getOption('dry-run')) {
165 12
            return $output->writeln('<info>Would have installed npm and bower dependencies</info>');
166
        }
167
168 12
        $this->getApplication()->find('rj_frontend:install')
169 12
            ->run(new ArrayInput(array('command' => 'rj_frontend:install')), $output);
170 12
    }
171
172 24
    private function createSourceTree($input, $output)
173
    {
174 24
        $blueprints = __DIR__.'/../Resources/blueprints';
175 24
        $dryRun = $input->getOption('dry-run');
176 24
        $base = $input->getOption('src-dir');
177
178 24
        $output->writeln($dryRun
179 12
            ? '<info>Would have created directory tree for source assets:</info>'
180 24
            : '<info>Creating directory tree for source assets:</info>'
181
        );
182
183 24
        $blueprintDir = "$blueprints/images";
184 24
        $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/images");
185
186 24
        $blueprintDir = "$blueprints/stylesheets/".$input->getOption('csspre');
187 24
        $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/stylesheets");
188
189 24
        $blueprintDir = "$blueprints/scripts/";
190 24
        $blueprintDir .= $input->getOption('coffee') ? 'coffee' : 'js';
191 24
        $this->createDirFromBlueprint($input, $output, $blueprintDir, "$base/scripts");
192
193 24
        $output->writeln('');
194 24
    }
195
196 24 View Code Duplication
    private function createBuildFile($input, $output)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
    {
198
        $files = array(
199 24
            'gulp' => 'gulp/gulpfile.js',
200
        );
201
202 24
        $this->createFileFromTemplate($input, $output, 'pipelines/'.$files[$input->getOption('pipeline')]);
203 24
    }
204
205 24 View Code Duplication
    private function createPackageJson($input, $output)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
    {
207
        $files = array(
208 24
            'gulp' => 'gulp/package.json',
209
        );
210
211 24
        $this->createFileFromTemplate($input, $output, 'pipelines/'.$files[$input->getOption('pipeline')]);
212 24
    }
213
214 24
    private function createBowerJson($input, $output)
215
    {
216 24
        $this->createFileFromTemplate($input, $output, 'bower.json');
217 24
    }
218
219 24
    private function createDirFromBlueprint($input, $output, $blueprintDir, $targetDir)
220
    {
221 24
        $dryRun = $input->getOption('dry-run');
222
223 24
        if (!$dryRun && !file_exists($targetDir)) {
224 12
            mkdir($targetDir, 0777, true);
225
        }
226
227 24
        foreach (preg_grep('/^\.?\w+/', scandir($blueprintDir)) as $entry) {
228 24
            $target = $entry;
229
230 24
            $isPhpTemplate = substr($entry, strrpos($entry, '.')) === '.php';
231 24
            if ($isPhpTemplate) {
232 24
                $entry = str_replace('.php', '', $entry);
233 24
                $target = str_replace('.php', '', $target);
234
            }
235
236 24
            $entry = $blueprintDir.'/'.$entry;
237 24
            $target = $targetDir.'/'.$target;
238
239 24
            if (!$dryRun) {
240 12
                if ($isPhpTemplate) {
241 12
                    $this->renderTemplate($input, $output, $entry, $target);
242
                } else {
243 12
                    if (file_exists($target) && !$input->getOption('force')) {
244
                        $output->writeln(
245
                            "<error>$target already exists. Run this command with --force to overwrite</error>
246
                        ");
247
248
                        continue;
249
                    }
250
251 12
                    copy($entry, $target);
252
                }
253
            }
254
255 24
            $output->writeln($target);
256
        }
257 24
    }
258
259 24
    private function createFileFromTemplate($input, $output, $file)
260
    {
261 24
        $dryRun = $input->getOption('dry-run');
262
263 24
        $targetFile = basename($file);
264 24
        if (!empty($this->rootDir)) {
265 24
            $targetFile = $this->rootDir.'/'.$targetFile;
266
        }
267
268 24
        $output->writeln($dryRun
269 12
            ? "<info>Would have created file $targetFile</info>"
270 24
            : "<info>Creating file $targetFile</info>"
271
        );
272
273 24
        if ($dryRun) {
274 12
            return;
275
        }
276
277 12
        $this->renderTemplate($input, $output, $file, $targetFile);
278 12
    }
279
280 12
    private function renderTemplate($input, $output, $file, $target)
281
    {
282 12
        if (file_exists($target) && !$input->getOption('force')) {
283 2
            return $output->writeln(
284
                "<error>$target already exists. Run this command with --force to overwrite</error>
285 2
            ");
286
        }
287
288 12
        switch ($input->getOption('csspre')) {
289
            case 'sass':
290 10
                $stylesheetExtension = 'scss';
291 10
                break;
292
            case 'less':
293 2
                $stylesheetExtension = 'less';
294 2
                break;
295
            default:
296
                $stylesheetExtension = 'css';
297
                break;
298
        }
299
300 12
        file_put_contents($target, $this->templating->render("$file.php", array(
301 12
            'projectName'         => basename(getcwd()),
302 12
            'srcDir'              => $input->getOption('src-dir'),
303 12
            'destDir'             => $input->getOption('dest-dir'),
304 12
            'prefix'              => str_replace('web/', '', $input->getOption('dest-dir')),
305 12
            'coffee'              => $input->getOption('coffee'),
306 12
            'cssPre'              => $input->getOption('csspre'),
307 12
            'stylesheetExtension' => $stylesheetExtension,
308
        )));
309 12
    }
310
311 30
    private function processOptions($input)
312
    {
313 30
        foreach ($input->getOptions() as $name => $value) {
314 30
            if (!$input->isInteractive() && $value === null) {
315 26
                $value = $this->getDefaultOption($name);
316
            }
317
318 30
            if ($value === 'true') {
319 4
                $value = true;
320 30
            } elseif ($value === 'false') {
321 24
                $value = false;
322
            }
323
324 30
            $input->setOption($name, $value);
325
        }
326 30
    }
327
328 30
    private function getDefaultOption($name)
329
    {
330
        $defaults = array(
331 30
            'src-dir'  => empty($this->rootDir) ? 'app/Resources' : $this->rootDir.'/app/Resources',
332 30
            'dest-dir' => empty($this->rootDir) ? 'web/assets' : $this->rootDir.'/web/assets',
333 30
            'pipeline' => 'gulp',
334 30
            'csspre'   => 'sass',
335 30
            'coffee'   => 'false',
336
        );
337
338 30
        return $defaults[$name];
339
    }
340
}
341