Completed
Push — master ( c2b5cc...89bfcf )
by Vladimir
03:33
created

Website::configureHighlighter()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 13
nc 4
nop 0
1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx;
9
10
use allejo\stakx\Command\BuildableCommand;
11
use allejo\stakx\Core\StakxLogger;
12
use allejo\stakx\Exception\FileAwareException;
13
use allejo\stakx\Manager\AssetManager;
14
use allejo\stakx\Manager\CollectionManager;
15
use allejo\stakx\Manager\DataManager;
16
use allejo\stakx\Manager\MenuManager;
17
use allejo\stakx\Manager\PageManager;
18
use allejo\stakx\Manager\ThemeManager;
19
use allejo\stakx\Manager\TwigManager;
20
use allejo\stakx\System\FileExplorer;
21
use allejo\stakx\System\Filesystem;
22
use allejo\stakx\System\Folder;
23
use allejo\stakx\Twig\StakxTwigTextProfiler;
24
use Highlight\Highlighter;
25
use Kwf\FileWatcher\Event\AbstractEvent;
26
use Kwf\FileWatcher\Event\Create;
27
use Kwf\FileWatcher\Event\Modify;
28
use Kwf\FileWatcher\Event\Move;
29
use Kwf\FileWatcher\Watcher;
30
use Symfony\Component\Console\Output\OutputInterface;
31
32
class Website
33
{
34
    /**
35
     * The location of where the compiled website will be written to.
36
     *
37
     * @var Folder
38
     */
39
    private $outputDirectory;
40
41
    /**
42
     * The main configuration to be used to build the specified website.
43
     *
44
     * @var Configuration
45
     */
46
    private $configuration;
47
48
    /**
49
     * When set to true, the Stakx website will be built without a configuration file.
50
     *
51
     * @var bool
52
     */
53
    private $confLess;
54
55
    /**
56
     * @var StakxLogger
57
     */
58
    private $output;
59
60
    /**
61
     * @var AssetManager
62
     */
63
    private $am;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $am. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
64
65
    /**
66
     * @var CollectionManager
67
     */
68
    private $cm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $cm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
69
70
    /**
71
     * @var DataManager
72
     */
73
    private $dm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $dm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
74
75
    /**
76
     * @var Filesystem
77
     */
78
    private $fs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
79
80
    /** @var MenuManager */
81
    private $mm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $mm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
82
83
    /**
84
     * @var PageManager
85
     */
86
    private $pm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $pm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
87
88
    /**
89
     * @var ThemeManager
90
     */
91
    private $tm;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $tm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
92
93
    /** @var Compiler */
94
    private $compiler;
95
96
    /**
97
     * Website constructor.
98
     *
99
     * @param OutputInterface $output
100
     */
101
    public function __construct(OutputInterface $output)
102
    {
103
        $this->output = new StakxLogger($output);
104
        $this->cm = new CollectionManager();
105
        $this->dm = new DataManager();
106
        $this->mm = new MenuManager();
107
        $this->pm = new PageManager();
108
        $this->fs = new Filesystem();
109
    }
110
111
    /**
112
     * Compile the website.
113
     *
114
     * @param bool $tracking Whether or not to keep track of files as they're compiled to save time in 'watch'
115
     */
116
    public function build($tracking = false)
117
    {
118
        Service::setParameter(BuildableCommand::WATCHING, $tracking);
119
120
        // Configure the environment
121
        $this->createFolderStructure();
122
        $this->configureHighlighter();
123
124
        // Our output directory
125
        $this->outputDirectory = new Folder($this->getConfiguration()->getTargetFolder());
126
        $this->outputDirectory->setTargetDirectory($this->getConfiguration()->getBaseUrl());
127
128
        // Parse DataItems
129
        $this->dm->setLogger($this->output);
130
        $this->dm->enableTracking($tracking);
131
        $this->dm->parseDataItems($this->getConfiguration()->getDataFolders());
132
        $this->dm->parseDataSets($this->getConfiguration()->getDataSets());
133
134
        // Prepare Collections
135
        $this->cm->setLogger($this->output);
136
        $this->cm->enableTracking($tracking);
137
        $this->cm->parseCollections($this->getConfiguration()->getCollectionsFolders());
138
139
        // Handle PageViews
140
        $this->pm->setLogger($this->output);
141
        $this->pm->enableTracking($tracking);
142
        $this->pm->setCollections($this->cm->getCollections());
143
        $this->pm->setDatasets($this->dm->getDataItems());
144
        $this->pm->parsePageViews($this->getConfiguration()->getPageViewFolders());
145
146
        // Handle the site's menu
147
        $this->mm->setLogger($this->output);
148
        $this->mm->buildFromPageViews($this->pm->getStaticPageViews());
149
150
        // Configure our Twig environment
151
        $theme = $this->configuration->getTheme();
152
        $twigEnv = new TwigManager();
153
        $twigEnv->configureTwig($this->getConfiguration(), array(
154
            'safe'    => Service::getParameter(BuildableCommand::SAFE_MODE),
155
            'globals' => array(
156
                array('name' => 'site', 'value' => $this->getConfiguration()->getConfiguration()),
157
                array('name' => 'collections', 'value' => $this->cm->getJailedCollections()),
158
                array('name' => 'menu', 'value' => $this->mm->getSiteMenu()),
159
                array('name' => 'pages', 'value' => $this->pm->getJailedStaticPageViews()),
160
                array('name' => 'data', 'value' => $this->dm->getJailedDataItems()),
161
            ),
162
        ));
163
164
        $profiler = null;
165
166
        if (Service::getParameter(BuildableCommand::BUILD_PROFILE))
167
        {
168
            $profiler = new \Twig_Profiler_Profile();
169
            TwigManager::getInstance()->addExtension(new \Twig_Extension_Profiler($profiler));
170
        }
171
172
        // Compile everything
173
        $this->compiler = new Compiler();
174
        $this->compiler->setLogger($this->output);
175
        $this->compiler->setRedirectTemplate($this->getConfiguration()->getRedirectTemplate());
176
        $this->compiler->setPageViews($this->pm->getPageViews(), $this->pm->getPageViewsFlattened());
177
        $this->compiler->setTargetFolder($this->outputDirectory);
178
        $this->compiler->setThemeName($theme);
179
        $this->compiler->compileAll();
180
181
        if (Service::getParameter(BuildableCommand::BUILD_PROFILE))
182
        {
183
            $dumper = new StakxTwigTextProfiler();
184
            $dumper->setTemplateMappings($this->compiler->getTemplateMappings());
185
            $text = $dumper->dump($profiler);
0 ignored issues
show
Bug introduced by
It seems like $profiler defined by null on line 164 can be null; however, Twig_Profiler_Dumper_Text::dump() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
186
            $this->output->writeln($text);
187
        }
188
189
        // At this point, we are looking at static files to copy over meaning we need to ignore all of the files that
190
        // make up the source of a stakx website
191
        $assetsToIgnore = array_merge(
192
            Configuration::$stakxSourceFiles,
193
            $this->getConfiguration()->getExcludes()
194
        );
195
196
        //
197
        // Theme Management
198
        //
199
        if (!is_null($theme))
200
        {
201
            $this->output->notice("Looking for '${theme}' theme...");
202
203
            $this->tm = new ThemeManager($theme);
204
            $this->tm->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
205
            $this->tm->setLogger($this->output);
206
            $this->tm->enableTracking($tracking);
207
            $this->tm->setFolder($this->outputDirectory);
208
            $this->tm->copyFiles();
209
        }
210
211
        //
212
        // Static file management
213
        //
214
        $this->am = new AssetManager();
215
        $this->am->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
216
        $this->am->setLogger($this->output);
217
        $this->am->setFolder($this->outputDirectory);
218
        $this->am->enableTracking($tracking);
219
        $this->am->copyFiles();
220
    }
221
222
    public function watch()
223
    {
224
        $this->output->writeln('Building website...');
225
        $this->build(true);
226
        $this->output->writeln(sprintf('Watching %s', getcwd()));
227
228
        $exclusions = array_merge($this->getConfiguration()->getExcludes(), array(
229
            $this->getConfiguration()->getTargetFolder()
230
        ));
231
        $fileExplorer = FileExplorer::create(
232
            getcwd(), $exclusions, $this->getConfiguration()->getIncludes()
233
        );
234
235
        $newWatcher = Watcher::create(getcwd());
236
        $newWatcher
237
            ->setLogger($this->output)
238
            ->setExcludePatterns(array_merge(
239
                $exclusions, FileExplorer::$vcsPatterns, array(Configuration::CACHE_FOLDER)
240
            ))
241
            ->setIterator($fileExplorer->getExplorer())
242
            ->addListener(Create::NAME, function ($e) { $this->watchListenerFunction($e); })
243
            ->addListener(Modify::NAME, function ($e) { $this->watchListenerFunction($e); })
244
            ->addListener(Move::NAME,   function ($e) { $this->watchListenerFunction($e); })
245
        ;
246
247
        $this->output->writeln('Watch started successfully');
248
249
        $newWatcher->start();
250
    }
251
252
    private function watchListenerFunction(AbstractEvent $event)
253
    {
254
        $filePath = $this->fs->getRelativePath($event->filename);
255
256
        try
257
        {
258
            switch ($event::getEventName())
259
            {
260
                case Create::NAME:
261
                    $this->creationWatcher($filePath);
262
                    break;
263
264
                case Modify::NAME:
265
                    $this->modificationWatcher($filePath);
266
                    break;
267
268
                case Move::NAME:
269
                    $newFile = $this->fs->getRelativePath($event->destFilename);
0 ignored issues
show
Bug introduced by
The property destFilename does not seem to exist. Did you mean filename?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
270
271
                    $this->deletionWatcher($filePath);
0 ignored issues
show
Unused Code introduced by
The call to the method allejo\stakx\Website::deletionWatcher() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
272
                    $this->creationWatcher($newFile);
273
                    break;
274
            }
275
        }
276
        catch (FileAwareException $e)
277
        {
278
            $this->output->writeln(sprintf("Your website failed to build with the following error in file '%s': %s",
279
                $e->getPath(),
280
                $e->getMessage()
281
            ));
282
        }
283
        catch (\Exception $e)
284
        {
285
            $this->output->writeln(sprintf('Your website failed to build with the following error: %s',
286
                $e->getMessage()
287
            ));
288
        }
289
    }
290
291
    /**
292
     * @return Configuration
293
     */
294
    public function getConfiguration()
295
    {
296
        return $this->configuration;
297
    }
298
299
    /**
300
     * @param string $configFile
301
     *
302
     * @throws \LogicException
303
     */
304
    public function setConfiguration($configFile)
305
    {
306
        if (!$this->fs->exists($configFile) && !$this->isConfLess())
307
        {
308
            $this->output->error('You are trying to build a website in a directory without a configuration file. Is this what you meant to do?');
309
            $this->output->error("To build a website without a configuration, use the '--no-conf' option");
310
311
            throw new \LogicException('Cannot build a website without a configuration when not in Configuration-less mode');
312
        }
313
314
        if ($this->isConfLess())
315
        {
316
            $configFile = '';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $configFile. This often makes code more readable.
Loading history...
317
        }
318
319
        $this->configuration = new Configuration();
320
        $this->configuration->setLogger($this->output);
321
        $this->configuration->parse($configFile);
322
    }
323
324
    /**
325
     * Get whether or not the website is being built in Configuration-less mode.
326
     *
327
     * @return bool True when being built with no configuration file
328
     */
329
    public function isConfLess()
330
    {
331
        return $this->confLess;
332
    }
333
334
    /**
335
     * Set whether or not the website should be built with a configuration.
336
     *
337
     * @param bool $status True when a website should be built without a configuration
338
     */
339
    public function setConfLess($status)
340
    {
341
        $this->confLess = $status;
342
    }
343
344
    /**
345
     * @param string $filePath
346
     */
347
    private function creationWatcher($filePath)
348
    {
349
        $this->output->writeln(sprintf('File creation detected: %s', $filePath));
350
351
        if ($this->pm->isHandled($filePath))
352
        {
353
            $this->pm->createNewItem($filePath);
354
            $this->pm->refreshItem($filePath);
355
        }
356
        elseif ($this->cm->isHandled($filePath))
357
        {
358
            $contentItem = $this->cm->createNewItem($filePath);
359
            TwigManager::getInstance()->addGlobal('collections', $this->cm->getCollections());
360
361
            $this->pm->trackNewContentItem($contentItem);
362
            $this->compiler->compileContentItem($contentItem);
0 ignored issues
show
Deprecated Code introduced by
The method allejo\stakx\Compiler::compileContentItem() has been deprecated.

This method has been deprecated.

Loading history...
363
            $this->compiler->compileSome(array(
364
                'namespace'  => 'collections',
365
                'dependency' => $contentItem->getNamespace(),
366
            ));
367
        }
368 View Code Duplication
        elseif ($this->dm->isHandled($filePath))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
369
        {
370
            $change = $this->dm->createNewItem($filePath);
371
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
372
373
            $this->compiler->compileSome(array(
374
                'namespace'  => 'data',
375
                'dependency' => $change,
376
            ));
377
        }
378
        elseif (!is_null($this->tm) && $this->tm->isHandled($filePath))
379
        {
380
            $this->tm->createNewItem($filePath);
381
        }
382
        elseif ($this->am->isHandled($filePath))
383
        {
384
            $this->am->createNewItem($filePath);
385
        }
386
    }
387
388
    /**
389
     * @param string $filePath
390
     */
391
    private function modificationWatcher($filePath)
392
    {
393
        $this->output->writeln(sprintf('File change detected: %s', $filePath));
394
395
        if ($this->compiler->isParentTemplate($filePath))
396
        {
397
            TwigManager::getInstance()->clearTemplateCache();
0 ignored issues
show
Deprecated Code introduced by
The method Twig_Environment::clearTemplateCache() has been deprecated with message: since 1.18.3 (to be removed in 2.0)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
398
            $this->compiler->refreshParent($filePath);
399
        }
400
        elseif ($this->pm->isTracked($filePath))
401
        {
402
            $change = $this->pm->refreshItem($filePath);
403
404
            TwigManager::getInstance()->clearTemplateCache();
0 ignored issues
show
Deprecated Code introduced by
The method Twig_Environment::clearTemplateCache() has been deprecated with message: since 1.18.3 (to be removed in 2.0)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
405
            $this->compiler->compilePageView($change);
406
        }
407
        elseif ($this->cm->isTracked($filePath))
408
        {
409
            $contentItem = &$this->cm->getContentItem($filePath);
410
            $contentItem->refreshFileContent();
411
412
            $this->compiler->compileContentItem($contentItem);
0 ignored issues
show
Deprecated Code introduced by
The method allejo\stakx\Compiler::compileContentItem() has been deprecated.

This method has been deprecated.

Loading history...
413
            $this->compiler->compileSome(array(
414
                'namespace'  => 'collections',
415
                'dependency' => $contentItem->getNamespace(),
416
            ));
417
        }
418 View Code Duplication
        elseif ($this->dm->isTracked($filePath))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
419
        {
420
            $change = $this->dm->refreshItem($filePath);
421
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
422
423
            $this->compiler->compileSome(array(
424
                'namespace'  => 'data',
425
                'dependency' => $change,
426
            ));
427
        }
428
        elseif (!is_null($this->tm) && $this->tm->isTracked($filePath))
429
        {
430
            $this->tm->refreshItem($filePath);
431
        }
432
        elseif ($this->am->isTracked($filePath))
433
        {
434
            $this->am->refreshItem($filePath);
435
        }
436
    }
437
438
    /**
439
     * @param string $filePath
440
     */
441
    private function deletionWatcher($filePath)
0 ignored issues
show
Unused Code introduced by
The parameter $filePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
442
    {
443
    }
444
445
    /**
446
     * Prepare the Stakx environment by creating necessary cache folders.
447
     */
448
    private function createFolderStructure()
449
    {
450
        $targetDir = $this->fs->absolutePath($this->configuration->getTargetFolder());
451
452
        if (!Service::getParameter(BuildableCommand::NO_CLEAN))
453
        {
454
            $this->fs->remove($targetDir);
455
        }
456
457
        if (!Service::getParameter(BuildableCommand::USE_CACHE))
458
        {
459
            $this->fs->remove($this->fs->absolutePath(Configuration::CACHE_FOLDER));
460
            $this->fs->mkdir($this->fs->absolutePath($this->fs->appendPath(Configuration::CACHE_FOLDER, 'twig')));
461
        }
462
463
        $this->fs->mkdir($targetDir);
464
    }
465
466
    private function configureHighlighter()
467
    {
468
        // Configure our highlighter
469
        Service::setParameter(Configuration::HIGHLIGHTER_ENABLED, $this->getConfiguration()->isHighlighterEnabled());
470
471
        if (Service::getParameter(Configuration::HIGHLIGHTER_ENABLED))
472
        {
473
            foreach ($this->getConfiguration()->getHighlighterCustomLanguages() as $lang => $path)
474
            {
475
                $fullPath = $this->fs->absolutePath($path);
476
477
                if (!$this->fs->exists($fullPath))
478
                {
479
                    $this->output->warning('The following language definition could not be found: {lang}', array(
480
                        'lang' => $path
481
                    ));
482
                    continue;
483
                }
484
485
                Highlighter::registerLanguage($lang, $fullPath);
486
                $this->output->debug('Loading custom language {lang} from {path}...', array(
487
                    'lang' => $lang,
488
                    'path' => $path
489
                ));
490
            }
491
        }
492
    }
493
}
494