• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Cecilapp / Cecil / 14532758805

18 Apr 2025 09:06AM UTC coverage: 83.633%. First build
14532758805

Pull #2148

github

web-flow
Merge 004f11f49 into bc7c717e4
Pull Request #2148: refactor: configuration and cache

315 of 376 new or added lines in 26 files covered. (83.78%)

3025 of 3617 relevant lines covered (83.63%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.0
/src/Builder.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <arnaud@ligny.fr>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13

14
namespace Cecil;
15

16
use Cecil\Collection\Page\Collection as PagesCollection;
17
use Cecil\Exception\RuntimeException;
18
use Cecil\Generator\GeneratorManager;
19
use Cecil\Logger\PrintLogger;
20
use Psr\Log\LoggerAwareInterface;
21
use Psr\Log\LoggerInterface;
22
use Symfony\Component\Finder\Finder;
23

24
/**
25
 * Class Builder.
26
 */
27
class Builder implements LoggerAwareInterface
28
{
29
    public const VERSION = '8.x-dev';
30
    public const VERBOSITY_QUIET = -1;
31
    public const VERBOSITY_NORMAL = 0;
32
    public const VERBOSITY_VERBOSE = 1;
33
    public const VERBOSITY_DEBUG = 2;
34

35
    /**
36
     * @var array Steps processed by build().
37
     */
38
    protected $steps = [
39
        'Cecil\Step\Pages\Load',
40
        'Cecil\Step\Data\Load',
41
        'Cecil\Step\StaticFiles\Load',
42
        'Cecil\Step\Pages\Create',
43
        'Cecil\Step\Pages\Convert',
44
        'Cecil\Step\Taxonomies\Create',
45
        'Cecil\Step\Pages\Generate',
46
        'Cecil\Step\Menus\Create',
47
        'Cecil\Step\StaticFiles\Copy',
48
        'Cecil\Step\Pages\Render',
49
        'Cecil\Step\Pages\Save',
50
        'Cecil\Step\Assets\Save',
51
        'Cecil\Step\Optimize\Html',
52
        'Cecil\Step\Optimize\Css',
53
        'Cecil\Step\Optimize\Js',
54
        'Cecil\Step\Optimize\Images',
55
    ];
56

57
    /** @var Config Configuration. */
58
    protected $config;
59

60
    /** @var LoggerInterface Logger. */
61
    protected $logger;
62

63
    /** @var bool Debug mode. */
64
    protected $debug = false;
65

66
    /** @var array Build options. */
67
    protected $options;
68

69
    /** @var Finder Content iterator. */
70
    protected $content;
71

72
    /** @var array Data collection. */
73
    protected $data = [];
74

75
    /** @var array Static files collection. */
76
    protected $static = [];
77

78
    /** @var PagesCollection Pages collection. */
79
    protected $pages;
80

81
    /** @var array Assets path collection */
82
    protected $assets = [];
83

84
    /** @var array Menus collection. */
85
    protected $menus;
86

87
    /** @var array Taxonomies collection. */
88
    protected $taxonomies;
89

90
    /** @var Renderer\RendererInterface Renderer. */
91
    protected $renderer;
92

93
    /** @var GeneratorManager Generators manager. */
94
    protected $generatorManager;
95

96
    /** @var string Application version. */
97
    protected static $version;
98

99
    /** @var array Build metrics. */
100
    protected $metrics = [];
101

102
    /**
103
     * @param Config|array|null    $config
104
     * @param LoggerInterface|null $logger
105
     */
106
    public function __construct($config = null, ?LoggerInterface $logger = null)
107
    {
108
        // init and set config
109
        $this->config = new Config();
1✔
110
        if ($config !== null) {
1✔
111
            $this->setConfig($config);
1✔
112
        }
113
        // debug mode?
114
        if (getenv('CECIL_DEBUG') == 'true' || $this->getConfig()->isEnabled('debug')) {
1✔
115
            $this->debug = true;
1✔
116
        }
117
        // set logger
118
        if ($logger === null) {
1✔
119
            $logger = new PrintLogger(self::VERBOSITY_VERBOSE);
×
120
        }
121
        $this->setLogger($logger);
1✔
122
    }
123

124
    /**
125
     * Creates a new Builder instance.
126
     */
127
    public static function create(): self
128
    {
129
        $class = new \ReflectionClass(\get_called_class());
1✔
130

131
        return $class->newInstanceArgs(\func_get_args());
1✔
132
    }
133

134
    /**
135
     * Builds a new website.
136
     */
137
    public function build(array $options): self
138
    {
139
        // set start script time and memory usage
140
        $startTime = microtime(true);
1✔
141
        $startMemory = memory_get_usage();
1✔
142

143
        // log warnings
144
        $this->logBuildWarnings();
1✔
145

146
        // prepare options
147
        $this->options = array_merge([
1✔
148
            'drafts'  => false, // build drafts or not
1✔
149
            'dry-run' => false, // if dry-run is true, generated files are not saved
1✔
150
            'page'    => '',    // specific page to build
1✔
151
        ], $options);
1✔
152

153
        // process each step
154
        $steps = [];
1✔
155
        // init...
156
        foreach ($this->steps as $step) {
1✔
157
            /** @var Step\StepInterface $stepObject */
158
            $stepObject = new $step($this);
1✔
159
            $stepObject->init($this->options);
1✔
160
            if ($stepObject->canProcess()) {
1✔
161
                $steps[] = $stepObject;
1✔
162
            }
163
        }
164
        // ...and process!
165
        $stepNumber = 0;
1✔
166
        $stepsTotal = \count($steps);
1✔
167
        foreach ($steps as $step) {
1✔
168
            $stepNumber++;
1✔
169
            /** @var Step\StepInterface $step */
170
            $this->getLogger()->notice($step->getName(), ['step' => [$stepNumber, $stepsTotal]]);
1✔
171
            $stepStartTime = microtime(true);
1✔
172
            $stepStartMemory = memory_get_usage();
1✔
173
            $step->process();
1✔
174
            // step duration and memory usage
175
            $this->metrics['steps'][$stepNumber]['name'] = $step->getName();
1✔
176
            $this->metrics['steps'][$stepNumber]['duration'] = Util::convertMicrotime((float) $stepStartTime);
1✔
177
            $this->metrics['steps'][$stepNumber]['memory']   = Util::convertMemory(memory_get_usage() - $stepStartMemory);
1✔
178
            $this->getLogger()->info(\sprintf(
1✔
179
                '%s done in %s (%s)',
1✔
180
                $this->metrics['steps'][$stepNumber]['name'],
1✔
181
                $this->metrics['steps'][$stepNumber]['duration'],
1✔
182
                $this->metrics['steps'][$stepNumber]['memory']
1✔
183
            ));
1✔
184
        }
185
        // build duration and memory usage
186
        $this->metrics['total']['duration'] = Util::convertMicrotime($startTime);
1✔
187
        $this->metrics['total']['memory']   = Util::convertMemory(memory_get_usage() - $startMemory);
1✔
188
        $this->getLogger()->notice(\sprintf('Built in %s (%s)', $this->metrics['total']['duration'], $this->metrics['total']['memory']));
1✔
189

190
        return $this;
1✔
191
    }
192

193
    /**
194
     * Set configuration.
195
     */
196
    public function setConfig(array|Config $config): self
197
    {
198
        if (\is_array($config)) {
1✔
199
            $config = new Config($config);
1✔
200
        }
201
        if ($this->config !== $config) {
1✔
202
            $this->config = $config;
1✔
203
        }
204

205
        // import themes configuration
206
        $this->importThemesConfig();
1✔
207
        // autoloads local extensions
208
        Util::autoload($this, 'extensions');
1✔
209

210
        return $this;
1✔
211
    }
212

213
    /**
214
     * Returns configuration.
215
     */
216
    public function getConfig(): Config
217
    {
218
        if ($this->config === null) {
1✔
NEW
219
            $this->config = new Config();
×
220
        }
221

222
        return $this->config;
1✔
223
    }
224

225
    /**
226
     * Config::setSourceDir() alias.
227
     */
228
    public function setSourceDir(string $sourceDir): self
229
    {
230
        $this->getConfig()->setSourceDir($sourceDir);
1✔
231
        // import themes configuration
232
        $this->importThemesConfig();
1✔
233

234
        return $this;
1✔
235
    }
236

237
    /**
238
     * Config::setDestinationDir() alias.
239
     */
240
    public function setDestinationDir(string $destinationDir): self
241
    {
242
        $this->getConfig()->setDestinationDir($destinationDir);
1✔
243

244
        return $this;
1✔
245
    }
246

247
    /**
248
     * Import themes configuration.
249
     */
250
    public function importThemesConfig(): void
251
    {
252
        foreach ($this->config->get('theme') as $theme) {
1✔
253
            $this->config->import(Config::loadFile(Util::joinFile($this->config->getThemesPath(), $theme, 'config.yml'), true), Config::PRESERVE);
1✔
254
        }
255
    }
256

257
    /**
258
     * {@inheritdoc}
259
     */
260
    public function setLogger(LoggerInterface $logger): void
261
    {
262
        $this->logger = $logger;
1✔
263
    }
264

265
    /**
266
     * Returns the logger instance.
267
     */
268
    public function getLogger(): LoggerInterface
269
    {
270
        return $this->logger;
1✔
271
    }
272

273
    /**
274
     * Returns debug mode state.
275
     */
276
    public function isDebug(): bool
277
    {
278
        return (bool) $this->debug;
1✔
279
    }
280

281
    /**
282
     * Returns build options.
283
     */
284
    public function getBuildOptions(): array
285
    {
286
        return $this->options;
1✔
287
    }
288

289
    /**
290
     * Set collected pages files.
291
     */
292
    public function setPagesFiles(Finder $content): void
293
    {
294
        $this->content = $content;
1✔
295
    }
296

297
    /**
298
     * Returns pages files.
299
     */
300
    public function getPagesFiles(): ?Finder
301
    {
302
        return $this->content;
1✔
303
    }
304

305
    /**
306
     * Set collected data.
307
     */
308
    public function setData(array $data): void
309
    {
310
        $this->data = $data;
1✔
311
    }
312

313
    /**
314
     * Returns data collection.
315
     */
316
    public function getData(?string $language = null): ?array
317
    {
318
        if ($language) {
1✔
319
            if (empty($this->data[$language])) {
1✔
320
                // fallback to default language
321
                return $this->data[$this->config->getLanguageDefault()];
1✔
322
            }
323

324
            return $this->data[$language];
1✔
325
        }
326

327
        return $this->data;
1✔
328
    }
329

330
    /**
331
     * Set collected static files.
332
     */
333
    public function setStatic(array $static): void
334
    {
335
        $this->static = $static;
1✔
336
    }
337

338
    /**
339
     * Returns static files collection.
340
     */
341
    public function getStatic(): array
342
    {
343
        return $this->static;
1✔
344
    }
345

346
    /**
347
     * Set/update Pages collection.
348
     */
349
    public function setPages(PagesCollection $pages): void
350
    {
351
        $this->pages = $pages;
1✔
352
    }
353

354
    /**
355
     * Returns pages collection.
356
     */
357
    public function getPages(): ?PagesCollection
358
    {
359
        return $this->pages;
1✔
360
    }
361

362
    /**
363
     * Set assets path list.
364
     */
365
    public function setAssets(array $assets): void
366
    {
367
        $this->assets = $assets;
×
368
    }
369

370
    /**
371
     * Add an asset path to assets list.
372
     */
373
    public function addAsset(string $path): void
374
    {
375
        if (!\in_array($path, $this->assets, true)) {
1✔
376
            $this->assets[] = $path;
1✔
377
        }
378
    }
379

380
    /**
381
     * Returns list of assets path.
382
     */
383
    public function getAssets(): ?array
384
    {
385
        return $this->assets;
1✔
386
    }
387

388
    /**
389
     * Set menus collection.
390
     */
391
    public function setMenus(array $menus): void
392
    {
393
        $this->menus = $menus;
1✔
394
    }
395

396
    /**
397
     * Returns all menus, for a language.
398
     */
399
    public function getMenus(string $language): Collection\Menu\Collection
400
    {
401
        return $this->menus[$language];
1✔
402
    }
403

404
    /**
405
     * Set taxonomies collection.
406
     */
407
    public function setTaxonomies(array $taxonomies): void
408
    {
409
        $this->taxonomies = $taxonomies;
1✔
410
    }
411

412
    /**
413
     * Returns taxonomies collection, for a language.
414
     */
415
    public function getTaxonomies(string $language): ?Collection\Taxonomy\Collection
416
    {
417
        return $this->taxonomies[$language];
1✔
418
    }
419

420
    /**
421
     * Set renderer object.
422
     */
423
    public function setRenderer(Renderer\RendererInterface $renderer): void
424
    {
425
        $this->renderer = $renderer;
1✔
426
    }
427

428
    /**
429
     * Returns Renderer object.
430
     */
431
    public function getRenderer(): Renderer\RendererInterface
432
    {
433
        return $this->renderer;
1✔
434
    }
435

436
    /**
437
     * Returns metrics array.
438
     */
439
    public function getMetrics(): array
440
    {
441
        return $this->metrics;
×
442
    }
443

444
    /**
445
     * Returns application version.
446
     *
447
     * @throws RuntimeException
448
     */
449
    public static function getVersion(): string
450
    {
451
        if (!isset(self::$version)) {
1✔
452
            try {
453
                $filePath = Util\File::getRealPath('VERSION');
1✔
454
                $version = Util\File::fileGetContents($filePath);
×
455
                if ($version === false) {
×
456
                    throw new RuntimeException(\sprintf('Can\'t read content of "%s".', $filePath));
×
457
                }
458
                self::$version = trim($version);
×
459
            } catch (\Exception) {
1✔
460
                self::$version = self::VERSION;
1✔
461
            }
462
        }
463

464
        return self::$version;
1✔
465
    }
466

467
    /**
468
     * Log build warnings.
469
     */
470
    protected function logBuildWarnings(): void
471
    {
472
        // baseurl
473
        if (empty(trim((string) $this->config->get('baseurl'), '/'))) {
1✔
474
            $this->getLogger()->warning('`baseurl` configuration key is required in production.');
×
475
        }
476
    }
477
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc