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

Cecilapp / Cecil / 13521796436

25 Feb 2025 12:58PM UTC coverage: 83.437%. First build
13521796436

Pull #2125

github

web-flow
Merge f93b4a652 into 6498cbb97
Pull Request #2125: feat: cmd options to display build steps metrics

12 of 13 new or added lines in 1 file covered. (92.31%)

2962 of 3550 relevant lines covered (83.44%)

0.83 hits per line

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

92.39
/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 Cecil\Util\Platform;
21
use Psr\Log\LoggerAwareInterface;
22
use Psr\Log\LoggerInterface;
23
use Symfony\Component\Finder\Finder;
24

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

36
    /**
37
     * @var array Steps processed by build().
38
     */
39
    protected $steps = [
40
        'Cecil\Step\Pages\Load',
41
        'Cecil\Step\Data\Load',
42
        'Cecil\Step\StaticFiles\Load',
43
        'Cecil\Step\Pages\Create',
44
        'Cecil\Step\Pages\Convert',
45
        'Cecil\Step\Taxonomies\Create',
46
        'Cecil\Step\Pages\Generate',
47
        'Cecil\Step\Menus\Create',
48
        'Cecil\Step\StaticFiles\Copy',
49
        'Cecil\Step\Pages\Render',
50
        'Cecil\Step\Pages\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 Menus collection. */
82
    protected $menus;
83

84
    /** @var array Taxonomies collection. */
85
    protected $taxonomies;
86

87
    /** @var Renderer\RendererInterface Renderer. */
88
    protected $renderer;
89

90
    /** @var GeneratorManager Generators manager. */
91
    protected $generatorManager;
92

93
    /** @var string Application version. */
94
    protected static $version;
95

96
    /** @var array Build metrics. */
97
    protected $metrics = [];
98

99
    /**
100
     * @param Config|array|null    $config
101
     * @param LoggerInterface|null $logger
102
     */
103
    public function __construct($config = null, ?LoggerInterface $logger = null)
104
    {
105
        // set logger
106
        if ($logger === null) {
1✔
107
            $logger = new PrintLogger(self::VERBOSITY_VERBOSE);
×
108
        }
109
        $this->setLogger($logger);
1✔
110
        // set config
111
        $this->setConfig($config)->setSourceDir(null)->setDestinationDir(null);
1✔
112
        // debug mode?
113
        if (getenv('CECIL_DEBUG') == 'true' || (bool) $this->getConfig()->get('debug')) {
1✔
114
            $this->debug = true;
1✔
115
        }
116
        // autoloads local extensions
117
        Util::autoload($this, 'extensions');
1✔
118
    }
119

120
    /**
121
     * Creates a new Builder instance.
122
     */
123
    public static function create(): self
124
    {
125
        $class = new \ReflectionClass(\get_called_class());
1✔
126

127
        return $class->newInstanceArgs(\func_get_args());
1✔
128
    }
129

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

139
        // log configuration errors
140
        $this->logConfigError();
1✔
141

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

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

186
        return $this;
1✔
187
    }
188

189
    /**
190
     * Set configuration.
191
     *
192
     * @param Config|array|null $config
193
     */
194
    public function setConfig($config): self
195
    {
196
        if (!$config instanceof Config) {
1✔
197
            $config = new Config($config);
1✔
198
        }
199
        if ($this->config !== $config) {
1✔
200
            $this->config = $config;
1✔
201
        }
202

203
        return $this;
1✔
204
    }
205

206
    /**
207
     * Returns configuration.
208
     */
209
    public function getConfig(): Config
210
    {
211
        return $this->config;
1✔
212
    }
213

214
    /**
215
     * Config::setSourceDir() alias.
216
     */
217
    public function setSourceDir(?string $sourceDir = null): self
218
    {
219
        $this->config->setSourceDir($sourceDir);
1✔
220

221
        return $this;
1✔
222
    }
223

224
    /**
225
     * Config::setDestinationDir() alias.
226
     */
227
    public function setDestinationDir(?string $destinationDir = null): self
228
    {
229
        $this->config->setDestinationDir($destinationDir);
1✔
230

231
        return $this;
1✔
232
    }
233

234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function setLogger(LoggerInterface $logger): void
238
    {
239
        $this->logger = $logger;
1✔
240
    }
241

242
    /**
243
     * Returns the logger instance.
244
     */
245
    public function getLogger(): LoggerInterface
246
    {
247
        return $this->logger;
1✔
248
    }
249

250
    /**
251
     * Returns debug mode state.
252
     */
253
    public function isDebug(): bool
254
    {
255
        return (bool) $this->debug;
1✔
256
    }
257

258
    /**
259
     * Returns build options.
260
     */
261
    public function getBuildOptions(): array
262
    {
263
        return $this->options;
1✔
264
    }
265

266
    /**
267
     * Set collected pages files.
268
     */
269
    public function setPagesFiles(Finder $content): void
270
    {
271
        $this->content = $content;
1✔
272
    }
273

274
    /**
275
     * Returns pages files.
276
     */
277
    public function getPagesFiles(): ?Finder
278
    {
279
        return $this->content;
1✔
280
    }
281

282
    /**
283
     * Set collected data.
284
     */
285
    public function setData(array $data): void
286
    {
287
        $this->data = $data;
1✔
288
    }
289

290
    /**
291
     * Returns data collection.
292
     */
293
    public function getData(?string $language = null): ?array
294
    {
295
        if ($language) {
1✔
296
            if (empty($this->data[$language])) {
1✔
297
                // fallback to default language
298
                return $this->data[$this->config->getLanguageDefault()];
1✔
299
            }
300

301
            return $this->data[$language];
1✔
302
        }
303

304
        return $this->data;
1✔
305
    }
306

307
    /**
308
     * Set collected static files.
309
     */
310
    public function setStatic(array $static): void
311
    {
312
        $this->static = $static;
1✔
313
    }
314

315
    /**
316
     * Returns static files collection.
317
     */
318
    public function getStatic(): array
319
    {
320
        return $this->static;
1✔
321
    }
322

323
    /**
324
     * Set/update Pages collection.
325
     */
326
    public function setPages(PagesCollection $pages): void
327
    {
328
        $this->pages = $pages;
1✔
329
    }
330

331
    /**
332
     * Returns pages collection.
333
     */
334
    public function getPages(): ?PagesCollection
335
    {
336
        return $this->pages;
1✔
337
    }
338

339
    /**
340
     * Set menus collection.
341
     */
342
    public function setMenus(array $menus): void
343
    {
344
        $this->menus = $menus;
1✔
345
    }
346

347
    /**
348
     * Returns all menus, for a language.
349
     */
350
    public function getMenus(string $language): Collection\Menu\Collection
351
    {
352
        return $this->menus[$language];
1✔
353
    }
354

355
    /**
356
     * Set taxonomies collection.
357
     */
358
    public function setTaxonomies(array $taxonomies): void
359
    {
360
        $this->taxonomies = $taxonomies;
1✔
361
    }
362

363
    /**
364
     * Returns taxonomies collection, for a language.
365
     */
366
    public function getTaxonomies(string $language): ?Collection\Taxonomy\Collection
367
    {
368
        return $this->taxonomies[$language];
1✔
369
    }
370

371
    /**
372
     * Set renderer object.
373
     */
374
    public function setRenderer(Renderer\RendererInterface $renderer): void
375
    {
376
        $this->renderer = $renderer;
1✔
377
    }
378

379
    /**
380
     * Returns Renderer object.
381
     */
382
    public function getRenderer(): Renderer\RendererInterface
383
    {
384
        return $this->renderer;
1✔
385
    }
386

387
    /**
388
     * Returns metrics array.
389
     */
390
    public function getMetrics(): array
391
    {
NEW
392
        return $this->metrics;
×
393
    }
394

395
    /**
396
     * Returns application version.
397
     *
398
     * @throws RuntimeException
399
     */
400
    public static function getVersion(): string
401
    {
402
        if (!isset(self::$version)) {
1✔
403
            $filePath = __DIR__ . '/../VERSION';
1✔
404
            if (Platform::isPhar()) {
1✔
405
                $filePath = Platform::getPharPath() . '/VERSION';
×
406
            }
407

408
            try {
409
                if (!file_exists($filePath)) {
1✔
410
                    throw new RuntimeException(\sprintf('File "%s" doesn\'t exist.', $filePath));
1✔
411
                }
412
                $version = Util\File::fileGetContents($filePath);
×
413
                if ($version === false) {
×
414
                    throw new RuntimeException(\sprintf('Can\'t get file "%s".', $filePath));
×
415
                }
416
                self::$version = trim($version);
×
417
            } catch (\Exception) {
1✔
418
                self::$version = self::VERSION;
1✔
419
            }
420
        }
421

422
        return self::$version;
1✔
423
    }
424

425
    /**
426
     * Log configuration errors.
427
     */
428
    protected function logConfigError(): void
429
    {
430
        // warning about baseurl
431
        if (empty(trim((string) $this->config->get('baseurl'), '/'))) {
1✔
432
            $this->getLogger()->warning('`baseurl` configuration key is required in production.');
1✔
433
        }
434
    }
435
}
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