Completed
Pull Request — master (#46)
by Vladimir
02:54
created

Website::watchListenerFunction()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.439
c 0
b 0
f 0
cc 5
eloc 17
nc 9
nop 1
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 Kwf\FileWatcher\Event\AbstractEvent;
24
use Kwf\FileWatcher\Event\Create;
25
use Kwf\FileWatcher\Event\Modify;
26
use Kwf\FileWatcher\Watcher;
27
use Symfony\Component\Console\Output\OutputInterface;
28
29
class Website
30
{
31
    /**
32
     * The location of where the compiled website will be written to.
33
     *
34
     * @var Folder
35
     */
36
    private $outputDirectory;
37
38
    /**
39
     * The main configuration to be used to build the specified website.
40
     *
41
     * @var Configuration
42
     */
43
    private $configuration;
44
45
    /**
46
     * When set to true, the Stakx website will be built without a configuration file.
47
     *
48
     * @var bool
49
     */
50
    private $confLess;
51
52
    /**
53
     * When set to true, Twig templates will not have access to filters or functions which provide access to the
54
     * filesystem.
55
     *
56
     * @var bool
57
     */
58
    private $safeMode;
59
60
    /**
61
     * When set to true, Stakx will not clean the _site folder after a rebuild.
62
     *
63
     * @var bool
64
     */
65
    private $noClean;
66
67
    /**
68
     * @var StakxLogger
69
     */
70
    private $output;
71
72
    /**
73
     * @var AssetManager
74
     */
75
    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...
76
77
    /**
78
     * @var CollectionManager
79
     */
80
    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...
81
82
    /**
83
     * @var DataManager
84
     */
85
    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...
86
87
    /**
88
     * @var Filesystem
89
     */
90
    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...
91
92
    /** @var MenuManager */
93
    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...
94
95
    /**
96
     * @var PageManager
97
     */
98
    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...
99
100
    /**
101
     * @var ThemeManager
102
     */
103
    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...
104
105
    /** @var Compiler */
106
    private $compiler;
107
108
    /**
109
     * Website constructor.
110
     *
111
     * @param OutputInterface $output
112
     */
113
    public function __construct(OutputInterface $output)
114
    {
115
        $this->output = new StakxLogger($output);
116
        $this->cm = new CollectionManager();
117
        $this->dm = new DataManager();
118
        $this->mm = new MenuManager();
119
        $this->pm = new PageManager();
120
        $this->fs = new Filesystem();
121
    }
122
123
    /**
124
     * Compile the website.
125
     *
126
     * @param bool $tracking Whether or not to keep track of files as they're compiled to save time in 'watch'
127
     */
128
    public function build($tracking = false)
129
    {
130
        Service::setParameter(BuildableCommand::WATCHING, $tracking);
131
132
        // Configure the environment
133
        $this->createFolderStructure(!$this->noClean);
134
135
        // Our output directory
136
        $this->outputDirectory = new Folder($this->getConfiguration()->getTargetFolder());
137
        $this->outputDirectory->setTargetDirectory($this->getConfiguration()->getBaseUrl());
138
139
        // Parse DataItems
140
        $this->dm->setLogger($this->output);
141
        $this->dm->enableTracking($tracking);
142
        $this->dm->parseDataItems($this->getConfiguration()->getDataFolders());
143
        $this->dm->parseDataSets($this->getConfiguration()->getDataSets());
144
145
        // Prepare Collections
146
        $this->cm->setLogger($this->output);
147
        $this->cm->enableTracking($tracking);
148
        $this->cm->parseCollections($this->getConfiguration()->getCollectionsFolders());
149
150
        // Handle PageViews
151
        $this->pm->setLogger($this->output);
152
        $this->pm->enableTracking($tracking);
153
        $this->pm->setCollections($this->cm->getCollections());
154
        $this->pm->parsePageViews($this->getConfiguration()->getPageViewFolders());
155
156
        // Handle the site's menu
157
        $this->mm->setLogger($this->output);
158
        $this->mm->buildFromPageViews($this->pm->getStaticPageViews());
159
160
        // Configure our Twig environment
161
        $theme = $this->configuration->getTheme();
162
        $twigEnv = new TwigManager();
163
        $twigEnv->configureTwig($this->getConfiguration(), array(
164
            'safe'    => $this->safeMode,
165
            'globals' => array(
166
                array('name' => 'site', 'value' => $this->getConfiguration()->getConfiguration()),
167
                array('name' => 'collections', 'value' => $this->cm->getJailedCollections()),
168
                array('name' => 'menu', 'value' => $this->mm->getSiteMenu()),
169
                array('name' => 'pages', 'value' => $this->pm->getJailedStaticPageViews()),
170
                array('name' => 'data', 'value' => $this->dm->getDataItems()),
171
            ),
172
        ));
173
174
        // Compile everything
175
        $this->compiler = new Compiler();
176
        $this->compiler->setLogger($this->output);
177
        $this->compiler->setRedirectTemplate($this->getConfiguration()->getRedirectTemplate());
178
        $this->compiler->setPageViews($this->pm->getPageViews(), $this->pm->getPageViewsFlattened());
179
        $this->compiler->setTargetFolder($this->outputDirectory);
180
        $this->compiler->setThemeName($theme);
181
        $this->compiler->compileAll();
182
183
        // At this point, we are looking at static files to copy over meaning we need to ignore all of the files that
184
        // make up the source of a stakx website
185
        $assetsToIgnore = array_merge(
186
            Configuration::$stakxSourceFiles,
187
            $this->getConfiguration()->getExcludes()
188
        );
189
190
        //
191
        // Theme Management
192
        //
193
        if (!is_null($theme))
194
        {
195
            $this->output->notice("Looking for '${theme}' theme...");
196
197
            $this->tm = new ThemeManager($theme);
198
            $this->tm->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
199
            $this->tm->setLogger($this->output);
200
            $this->tm->enableTracking($tracking);
201
            $this->tm->setFolder($this->outputDirectory);
202
            $this->tm->copyFiles();
203
        }
204
205
        //
206
        // Static file management
207
        //
208
        $this->am = new AssetManager();
209
        $this->am->configureFinder($this->getConfiguration()->getIncludes(), $assetsToIgnore);
210
        $this->am->setLogger($this->output);
211
        $this->am->setFolder($this->outputDirectory);
212
        $this->am->enableTracking($tracking);
213
        $this->am->copyFiles();
214
    }
215
216
    public function watch()
217
    {
218
        $this->output->writeln('Building website...');
219
        $this->build(true);
220
        $this->output->writeln(sprintf('Watching %s', getcwd()));
221
222
        $exclusions = array_merge($this->getConfiguration()->getExcludes(), array(
223
            $this->getConfiguration()->getTargetFolder(),
224
        ));
225
        $fileExplorer = FileExplorer::create(
226
            getcwd(), $exclusions, $this->getConfiguration()->getIncludes()
227
        );
228
229
        $newWatcher = Watcher::create(getcwd());
230
        $newWatcher
231
            ->setExcludePatterns($exclusions)
232
            ->setIterator($fileExplorer->getExplorer())
233
            ->addListener(Create::NAME, function ($e) { $this->watchListenerFunction($e); })
234
            ->addListener(Modify::NAME, function ($e) { $this->watchListenerFunction($e); })
235
        ;
236
237
        $this->output->writeln('Watch started successfully');
238
239
        $newWatcher->start();
240
    }
241
242
    private function watchListenerFunction(AbstractEvent $event)
243
    {
244
        $filePath = $this->fs->getRelativePath($event->filename);
245
246
        try
247
        {
248
            switch ($event::getEventName())
249
            {
250
                case Create::NAME:
251
                    $this->creationWatcher($filePath);
252
                    break;
253
254
                case Modify::NAME:
255
                    $this->modificationWatcher($filePath);
256
                    break;
257
            }
258
        }
259
        catch (FileAwareException $e)
260
        {
261
            $this->output->writeln(sprintf("Your website failed to build with the following error in file '%s': %s",
262
                $e->getPath(),
263
                $e->getMessage()
264
            ));
265
        }
266
        catch (\Exception $e)
267
        {
268
            $this->output->writeln(sprintf('Your website failed to build with the following error: %s',
269
                $e->getMessage()
270
            ));
271
        }
272
    }
273
274
    /**
275
     * @return Configuration
276
     */
277
    public function getConfiguration()
278
    {
279
        return $this->configuration;
280
    }
281
282
    /**
283
     * @param string $configFile
284
     *
285
     * @throws \LogicException
286
     */
287
    public function setConfiguration($configFile)
288
    {
289
        if (!$this->fs->exists($configFile) && !$this->isConfLess())
290
        {
291
            $this->output->error('You are trying to build a website in a directory without a configuration file. Is this what you meant to do?');
292
            $this->output->error("To build a website without a configuration, use the '--no-conf' option");
293
294
            throw new \LogicException('Cannot build a website without a configuration when not in Configuration-less mode');
295
        }
296
297
        if ($this->isConfLess())
298
        {
299
            $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...
300
        }
301
302
        $this->configuration = new Configuration();
303
        $this->configuration->setLogger($this->output);
304
        $this->configuration->parse($configFile);
305
    }
306
307
    /**
308
     * Get whether or not the website is being built in Configuration-less mode.
309
     *
310
     * @return bool True when being built with no configuration file
311
     */
312
    public function isConfLess()
313
    {
314
        return $this->confLess;
315
    }
316
317
    /**
318
     * Set whether or not the website should be built with a configuration.
319
     *
320
     * @param bool $status True when a website should be built without a configuration
321
     */
322
    public function setConfLess($status)
323
    {
324
        $this->confLess = $status;
325
    }
326
327
    /**
328
     * Get whether or not the website is being built in safe mode.
329
     *
330
     * Safe mode is defined as disabling file system access from Twig and disabling user Twig extensions
331
     *
332
     * @return bool True when the website is being built in safe mode
333
     */
334
    public function isSafeMode()
335
    {
336
        return $this->safeMode;
337
    }
338
339
    /**
340
     * Set whether a website should be built in safe mode.
341
     *
342
     * @param bool $bool True if a website should be built in safe mode
343
     */
344
    public function setSafeMode($bool)
345
    {
346
        $this->safeMode = $bool;
347
    }
348
349
    /**
350
     * @return bool
351
     */
352
    public function isNoClean()
353
    {
354
        return $this->noClean;
355
    }
356
357
    /**
358
     * @param bool $noClean
359
     */
360
    public function setNoClean($noClean)
361
    {
362
        $this->noClean = $noClean;
363
    }
364
365
    private function creationWatcher($filePath)
366
    {
367
        $this->output->writeln(sprintf('File creation detected: %s', $filePath));
368
369
        if ($this->pm->isHandled($filePath))
370
        {
371
            $this->pm->createNewItem($filePath);
372
            $this->pm->refreshItem($filePath);
373
        }
374
        elseif ($this->cm->isHandled($filePath))
375
        {
376
            $contentItem = $this->cm->createNewItem($filePath);
377
            TwigManager::getInstance()->addGlobal('collections', $this->cm->getCollections());
378
379
            $this->pm->trackNewContentItem($contentItem);
380
            $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...
381
            $this->compiler->compileSome(array(
382
                'namespace'  => 'collections',
383
                'dependency' => $contentItem->getCollection(),
384
            ));
385
        }
386 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...
387
        {
388
            $change = $this->dm->createNewItem($filePath);
389
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
390
391
            $this->compiler->compileSome(array(
392
                'namespace'  => 'data',
393
                'dependency' => $change,
394
            ));
395
        }
396
        elseif (!is_null($this->tm) && $this->tm->isHandled($filePath))
397
        {
398
            $this->tm->createNewItem($filePath);
399
        }
400
        elseif ($this->am->isHandled($filePath))
401
        {
402
            $this->am->createNewItem($filePath);
403
        }
404
    }
405
406
    private function modificationWatcher($filePath)
407
    {
408
        $this->output->writeln(sprintf('File change detected: %s', $filePath));
409
410
        if ($this->compiler->isParentTemplate($filePath))
411
        {
412
            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...
413
            $this->compiler->refreshParent($filePath);
414
        }
415
        elseif ($this->pm->isTracked($filePath))
416
        {
417
            $change = $this->pm->refreshItem($filePath);
418
419
            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...
420
            $this->compiler->compilePageView($change);
421
        }
422
        elseif ($this->cm->isTracked($filePath))
423
        {
424
            $contentItem = &$this->cm->getContentItem($filePath);
425
            $contentItem->refreshFileContent();
426
427
            $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...
428
            $this->compiler->compileSome(array(
429
                'namespace'  => 'collections',
430
                'dependency' => $contentItem->getCollection(),
431
            ));
432
        }
433 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...
434
        {
435
            $change = $this->dm->refreshItem($filePath);
436
            TwigManager::getInstance()->addGlobal('data', $this->dm->getDataItems());
437
438
            $this->compiler->compileSome(array(
439
                'namespace'  => 'data',
440
                'dependency' => $change,
441
            ));
442
        }
443
        elseif (!is_null($this->tm) && $this->tm->isTracked($filePath))
444
        {
445
            $this->tm->refreshItem($filePath);
446
        }
447
        elseif ($this->am->isTracked($filePath))
448
        {
449
            $this->am->refreshItem($filePath);
450
        }
451
    }
452
453
    /**
454
     * Prepare the Stakx environment by creating necessary cache folders.
455
     *
456
     * @param bool $cleanDirectory Clean the target directory
457
     */
458
    private function createFolderStructure($cleanDirectory)
459
    {
460
        $tarDir = $this->fs->absolutePath($this->configuration->getTargetFolder());
461
462
        if ($cleanDirectory)
463
        {
464
            $this->fs->remove($tarDir);
465
        }
466
467
        $this->fs->remove($this->fs->absolutePath('.stakx-cache'));
468
        $this->fs->mkdir($this->fs->absolutePath('.stakx-cache/twig'));
469
        $this->fs->mkdir($tarDir);
470
    }
471
}
472