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

Cecilapp / Cecil / 14658743998

25 Apr 2025 06:54AM UTC coverage: 83.658%. First build
14658743998

Pull #2164

github

web-flow
Merge c1e7a85bf into 7cb2621ca
Pull Request #2164: Serve & Build Commands : Improve metrics command

2 of 7 new or added lines in 1 file covered. (28.57%)

3051 of 3647 relevant lines covered (83.66%)

0.84 hits per line

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

87.16
/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
    /** @var string curent build ID */
103
    protected $buildId;
104

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

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

134
        return $class->newInstanceArgs(\func_get_args());
1✔
135
    }
136

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

146
        // log warnings
147
        $this->logBuildWarnings();
1✔
148

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

157
        // set build ID
158
        $this->buildId = date('YmdHis');
1✔
159

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

204
        return $this;
1✔
205
    }
206

207
    /**
208
     * Returns current build ID.
209
     */
210
    public function getBuilId(): string
211
    {
212
        return $this->buildId;
1✔
213
    }
214

215
    /**
216
     * Set configuration.
217
     */
218
    public function setConfig(array|Config $config): self
219
    {
220
        if (\is_array($config)) {
1✔
221
            $config = new Config($config);
1✔
222
        }
223
        if ($this->config !== $config) {
1✔
224
            $this->config = $config;
1✔
225
        }
226

227
        // import themes configuration
228
        $this->importThemesConfig();
1✔
229
        // autoloads local extensions
230
        Util::autoload($this, 'extensions');
1✔
231

232
        return $this;
1✔
233
    }
234

235
    /**
236
     * Returns configuration.
237
     */
238
    public function getConfig(): Config
239
    {
240
        if ($this->config === null) {
1✔
241
            $this->config = new Config();
×
242
        }
243

244
        return $this->config;
1✔
245
    }
246

247
    /**
248
     * Config::setSourceDir() alias.
249
     */
250
    public function setSourceDir(string $sourceDir): self
251
    {
252
        $this->getConfig()->setSourceDir($sourceDir);
1✔
253
        // import themes configuration
254
        $this->importThemesConfig();
1✔
255

256
        return $this;
1✔
257
    }
258

259
    /**
260
     * Config::setDestinationDir() alias.
261
     */
262
    public function setDestinationDir(string $destinationDir): self
263
    {
264
        $this->getConfig()->setDestinationDir($destinationDir);
1✔
265

266
        return $this;
1✔
267
    }
268

269
    /**
270
     * Import themes configuration.
271
     */
272
    public function importThemesConfig(): void
273
    {
274
        foreach ($this->config->get('theme') as $theme) {
1✔
275
            $this->config->import(Config::loadFile(Util::joinFile($this->config->getThemesPath(), $theme, 'config.yml'), true), Config::PRESERVE);
1✔
276
        }
277
    }
278

279
    /**
280
     * {@inheritdoc}
281
     */
282
    public function setLogger(LoggerInterface $logger): void
283
    {
284
        $this->logger = $logger;
1✔
285
    }
286

287
    /**
288
     * Returns the logger instance.
289
     */
290
    public function getLogger(): LoggerInterface
291
    {
292
        return $this->logger;
1✔
293
    }
294

295
    /**
296
     * Returns debug mode state.
297
     */
298
    public function isDebug(): bool
299
    {
300
        return (bool) $this->debug;
1✔
301
    }
302

303
    /**
304
     * Returns build options.
305
     */
306
    public function getBuildOptions(): array
307
    {
308
        return $this->options;
1✔
309
    }
310

311
    /**
312
     * Set collected pages files.
313
     */
314
    public function setPagesFiles(Finder $content): void
315
    {
316
        $this->content = $content;
1✔
317
    }
318

319
    /**
320
     * Returns pages files.
321
     */
322
    public function getPagesFiles(): ?Finder
323
    {
324
        return $this->content;
1✔
325
    }
326

327
    /**
328
     * Set collected data.
329
     */
330
    public function setData(array $data): void
331
    {
332
        $this->data = $data;
1✔
333
    }
334

335
    /**
336
     * Returns data collection.
337
     */
338
    public function getData(?string $language = null): ?array
339
    {
340
        if ($language) {
1✔
341
            if (empty($this->data[$language])) {
1✔
342
                // fallback to default language
343
                return $this->data[$this->config->getLanguageDefault()];
1✔
344
            }
345

346
            return $this->data[$language];
1✔
347
        }
348

349
        return $this->data;
1✔
350
    }
351

352
    /**
353
     * Set collected static files.
354
     */
355
    public function setStatic(array $static): void
356
    {
357
        $this->static = $static;
1✔
358
    }
359

360
    /**
361
     * Returns static files collection.
362
     */
363
    public function getStatic(): array
364
    {
365
        return $this->static;
1✔
366
    }
367

368
    /**
369
     * Set/update Pages collection.
370
     */
371
    public function setPages(PagesCollection $pages): void
372
    {
373
        $this->pages = $pages;
1✔
374
    }
375

376
    /**
377
     * Returns pages collection.
378
     */
379
    public function getPages(): ?PagesCollection
380
    {
381
        return $this->pages;
1✔
382
    }
383

384
    /**
385
     * Set assets path list.
386
     */
387
    public function setAssets(array $assets): void
388
    {
389
        $this->assets = $assets;
×
390
    }
391

392
    /**
393
     * Add an asset path to assets list.
394
     */
395
    public function addAsset(string $path): void
396
    {
397
        if (!\in_array($path, $this->assets, true)) {
1✔
398
            $this->assets[] = $path;
1✔
399
        }
400
    }
401

402
    /**
403
     * Returns list of assets path.
404
     */
405
    public function getAssets(): ?array
406
    {
407
        return $this->assets;
1✔
408
    }
409

410
    /**
411
     * Set menus collection.
412
     */
413
    public function setMenus(array $menus): void
414
    {
415
        $this->menus = $menus;
1✔
416
    }
417

418
    /**
419
     * Returns all menus, for a language.
420
     */
421
    public function getMenus(string $language): Collection\Menu\Collection
422
    {
423
        return $this->menus[$language];
1✔
424
    }
425

426
    /**
427
     * Set taxonomies collection.
428
     */
429
    public function setTaxonomies(array $taxonomies): void
430
    {
431
        $this->taxonomies = $taxonomies;
1✔
432
    }
433

434
    /**
435
     * Returns taxonomies collection, for a language.
436
     */
437
    public function getTaxonomies(string $language): ?Collection\Taxonomy\Collection
438
    {
439
        return $this->taxonomies[$language];
1✔
440
    }
441

442
    /**
443
     * Set renderer object.
444
     */
445
    public function setRenderer(Renderer\RendererInterface $renderer): void
446
    {
447
        $this->renderer = $renderer;
1✔
448
    }
449

450
    /**
451
     * Returns Renderer object.
452
     */
453
    public function getRenderer(): Renderer\RendererInterface
454
    {
455
        return $this->renderer;
1✔
456
    }
457

458
    /**
459
     * Returns metrics array.
460
     */
461
    public function getMetrics(): array
462
    {
463
        return $this->metrics;
×
464
    }
465

466
    /**
467
     * Returns application version.
468
     *
469
     * @throws RuntimeException
470
     */
471
    public static function getVersion(): string
472
    {
473
        if (!isset(self::$version)) {
1✔
474
            try {
475
                $filePath = Util\File::getRealPath('VERSION');
1✔
476
                $version = Util\File::fileGetContents($filePath);
×
477
                if ($version === false) {
×
478
                    throw new RuntimeException(\sprintf('Can\'t read content of "%s".', $filePath));
×
479
                }
480
                self::$version = trim($version);
×
481
            } catch (\Exception) {
1✔
482
                self::$version = self::VERSION;
1✔
483
            }
484
        }
485

486
        return self::$version;
1✔
487
    }
488

489
    /**
490
     * Log build warnings.
491
     */
492
    protected function logBuildWarnings(): void
493
    {
494
        // baseurl
495
        if (empty(trim((string) $this->config->get('baseurl'), '/'))) {
1✔
496
            $this->getLogger()->warning('`baseurl` configuration key is required in production.');
×
497
        }
498
    }
499
}
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

© 2026 Coveralls, Inc