Issues (16)

Branch: analysis-2ZGKmg

src/Command/Build.php (2 issues)

Labels
Severity
1
<?php
2
3
/**
4
 * This file is part of Cecil.
5
 *
6
 * (c) Arnaud Ligny <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Cecil\Command;
15
16
use Cecil\Util;
17
use Symfony\Component\Console\Helper\Table;
18
use Symfony\Component\Console\Input\InputArgument;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\InputOption;
21
use Symfony\Component\Console\Output\OutputInterface;
22
23
/**
24
 * Build command.
25
 *
26
 * This command generates the website in the output directory.
27
 * It can include drafts, optimize generated files, and perform a dry run.
28
 * It also allows building a specific page or a subset of pages, clearing the cache, and showing build metrics.
29
 */
30
class Build extends AbstractCommand
31
{
32
    /**
33
     * {@inheritdoc}
34
     */
35
    protected function configure()
36
    {
37
        $this
38
            ->setName('build')
39
            ->setDescription('Builds the website')
40
            ->setDefinition([
41
                new InputArgument('path', InputArgument::OPTIONAL, 'Use the given path as working directory'),
42
                new InputOption('drafts', 'd', InputOption::VALUE_NONE, 'Include drafts'),
43
                new InputOption('baseurl', 'u', InputOption::VALUE_REQUIRED, 'Set the base URL'),
44
                new InputOption('output', 'o', InputOption::VALUE_REQUIRED, 'Set the output directory'),
45
                new InputOption('optimize', null, InputOption::VALUE_NEGATABLE, 'Enable (or disable --no-optimize) optimization of generated files'),
46
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Build without saving'),
47
                new InputOption('config', 'c', InputOption::VALUE_REQUIRED, 'Set the path to extra config files (comma-separated)'),
48
                new InputOption('clear-cache', null, InputOption::VALUE_OPTIONAL, 'Clear cache before build (optional cache key as regular expression)', false),
49
                new InputOption('page', 'p', InputOption::VALUE_REQUIRED, 'Build a specific page'),
50
                new InputOption('render-subset', null, InputOption::VALUE_REQUIRED, 'Render a subset of pages'),
51
                new InputOption('show-pages', null, InputOption::VALUE_NONE, 'Show list of built pages in a table'),
52
                new InputOption('metrics', 'm', InputOption::VALUE_NONE, 'Show build metrics (duration and memory) of each step'),
53
            ])
54
            ->setHelp(
55
                <<<'EOF'
56
The <info>%command.name%</> command generates the website in the <comment>output</comment> directory.
57
58
  <info>%command.full_name%</>
59
  <info>%command.full_name% path/to/the/working/directory</>
60
  <info>%command.full_name% --baseurl=https://example.com/</>
61
  <info>%command.full_name% --output=_site</>
62
63
To build the website with <comment>optimization</comment> of generated files, you can use the <info>--optimize</info> option.
64
This is useful to reduce the size of the generated files and <comment>improve performance</comment>:
65
66
  <info>%command.full_name% --optimize</>
67
  <info>%command.full_name% --no-optimize</>
68
69
To build the website <comment>without overwriting files in the output</comment> directory, you can use the <info>--dry-run</info> option.
70
This is useful to check what would be built without actually writing files:
71
72
  <info>%command.full_name% --dry-run</>
73
74
To build the website with a specific subset of rendered pages, you can use the <info>--render-subset</info> option.
75
This is useful to <comment>build only a part of the website</comment>, for example, only "hot" pages or a specific section:
76
77
  <info>%command.full_name% --render-subset=subset</>
78
79
To show build steps <comment>metrics</comment>, run:
80
81
  <info>%command.full_name% --metrics</>
82
EOF
83
            );
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    protected function execute(InputInterface $input, OutputInterface $output)
90
    {
91
        $config = [];
92
        $options = [];
93
        $messageOpt = '';
94
95
        if ($input->getOption('baseurl')) {
96
            $config['baseurl'] = $input->getOption('baseurl');
97
        }
98
        if ($input->getOption('output')) {
99
            $config['output']['dir'] = $input->getOption('output');
100
            Util\File::getFS()->dumpFile(Util::joinFile($this->getPath(), self::TMP_DIR, 'output'), (string) $input->getOption('output'));
101
        }
102
        if ($input->getOption('optimize') === true) {
103
            $config['optimize']['enabled'] = true;
104
        }
105
        if ($input->getOption('optimize') === false) {
106
            $config['optimize']['enabled'] = false;
107
        }
108
        if ($input->getOption('clear-cache') === null) {
109
            $config['cache']['enabled'] = false;
110
        }
111
112
        $builder = $this->getBuilder($config);
113
114
        if ($input->getOption('drafts')) {
115
            $options['drafts'] = true;
116
            $messageOpt .= ' with drafts';
117
        }
118
        if ($input->getOption('dry-run')) {
119
            $options['dry-run'] = true;
120
            $messageOpt .= ' (dry-run)';
121
        }
122
        if ($input->getOption('page')) {
123
            $options['page'] = $input->getOption('page');
124
        }
125
        if ($input->getOption('render-subset')) {
126
            $options['render-subset'] = (string) $input->getOption('render-subset');
127
        }
128
        if ($input->getOption('clear-cache')) {
129
            if (0 < $removedFiles = (new \Cecil\Assets\Cache($this->getBuilder()))->clearByPattern((string) $input->getOption('clear-cache'))) {
130
                $output->writeln(\sprintf('<info>%s cache files removed by regular expression "%s"</info>', $removedFiles, $input->getOption('clear-cache')));
131
            }
132
        }
133
134
        $output->writeln(\sprintf('Building website%s...', $messageOpt));
135
        $output->writeln(\sprintf('<comment>Path:   %s</comment>', $this->getPath()), OutputInterface::VERBOSITY_VERY_VERBOSE);
136
        if (!empty($this->getConfigFiles())) {
137
            $output->writeln(\sprintf('<comment>Config: %s</comment>', implode(', ', $this->getConfigFiles())), OutputInterface::VERBOSITY_VERY_VERBOSE);
138
        }
139
        $output->writeln(\sprintf('<comment>Output: %s</comment>', $this->getBuilder()->getConfig()->getOutputPath()), OutputInterface::VERBOSITY_VERY_VERBOSE);
140
        if ($builder->getConfig()->isEnabled('cache') !== false) {
141
            $output->writeln(\sprintf('<comment>Cache:  %s</comment>', $builder->getConfig()->getCachePath()), OutputInterface::VERBOSITY_VERY_VERBOSE);
142
        }
143
144
        // build
145
        $builder->build($options);
146
        $output->writeln('Done 🎉');
147
148
        // show build steps metrics
149
        if ($input->getOption('metrics')) {
150
            $table = new Table($output);
151
            $table
152
                ->setHeaderTitle('Build steps metrics')
153
                ->setHeaders(['Step', 'Duration', 'Memory'])
154
                ->setRows($builder->getMetrics()['steps'])
155
            ;
156
            $table->setStyle('box')->render();
157
        }
158
159
        // show built pages as table
160
        if ($input->getOption('show-pages')) {
161
            $pagesAsArray = [];
162
            foreach (
163
                $this->getBuilder()->getPages()->filter(function (\Cecil\Collection\Page\Page $page) {
164
                    return $page->getVariable('published');
165
                })->usort(function (\Cecil\Collection\Page\Page $pageA, \Cecil\Collection\Page\Page $pageB) {
166
                    return strnatcmp($pageA['language'], $pageB['language']);
0 ignored issues
show
It seems like $pageA['language'] can also be of type null; however, parameter $string1 of strnatcmp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

166
                    return strnatcmp(/** @scrutinizer ignore-type */ $pageA['language'], $pageB['language']);
Loading history...
It seems like $pageB['language'] can also be of type null; however, parameter $string2 of strnatcmp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

166
                    return strnatcmp($pageA['language'], /** @scrutinizer ignore-type */ $pageB['language']);
Loading history...
167
                }) as $page
168
            ) {
169
                /** @var \Cecil\Collection\Page\Page $page */
170
                $pagesAsArray[] = [
171
                    $page->getId(),
172
                    $page->getVariable('language'),
173
                    \sprintf("%s %s", $page->getType(), $page->getType() !== \Cecil\Collection\Page\Type::PAGE->value ? "(" . \count($page->getPages() ?: []) . ")" : ''),
174
                    $page->getSection(),
175
                    $page->isVirtual() ? 'true' : 'false',
176
                ];
177
            }
178
            $table = new Table($output);
179
            $table
180
                ->setHeaderTitle(\sprintf("Built pages (%s)", \count($pagesAsArray)))
181
                ->setHeaders(['ID', 'Lang', 'Type', 'Section', 'Virtual'])
182
                ->setRows($pagesAsArray)
183
            ;
184
            $table->setStyle('box')->render();
185
        }
186
187
        return 0;
188
    }
189
}
190