Passed
Pull Request — master (#1881)
by Arnaud
09:13 queued 03:46
created

AbstractCommand::findConfigFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <[email protected]>
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\Command;
15
16
use Cecil\Builder;
17
use Cecil\Exception\RuntimeException;
18
use Cecil\Logger\ConsoleLogger;
19
use Cecil\Util;
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Style\SymfonyStyle;
24
use Symfony\Component\Filesystem\Path;
25
use Symfony\Component\Process\Process;
26
use Symfony\Component\Yaml\Exception\ParseException;
27
use Symfony\Component\Yaml\Yaml;
28
29
class AbstractCommand extends Command
30
{
31
    public const CONFIG_FILE = ['cecil.yml', 'config.yml'];
32
    public const TMP_DIR = '.cecil';
33
34
    /** @var InputInterface */
35
    protected $input;
36
37
    /** @var OutputInterface */
38
    protected $output;
39
40
    /** @var SymfonyStyle */
41
    protected $io;
42
43
    /** @var null|string */
44
    private $path = null;
45
46
    /** @var array */
47
    private $configFiles;
48
49
    /** @var array */
50
    private $config;
51
52
    /** @var Builder */
53
    private $builder;
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    protected function initialize(InputInterface $input, OutputInterface $output)
59
    {
60
        $this->input = $input;
61
        $this->output = $output;
62
        $this->io = new SymfonyStyle($input, $output);
63
64
        // set up configuration
65
        if (!\in_array($this->getName(), ['serve', 'new:site', 'self-update'])) {
66
            // default configuration file
67
            $this->configFiles[$this->findConfigFile('name')] = $this->findConfigFile('path');
68
            // from --config=<file>
69
            if ($input->hasOption('config') && $input->getOption('config') !== null) {
70
                foreach (explode(',', (string) $input->getOption('config')) as $configFile) {
71
                    $this->configFiles[$configFile] = realpath($configFile);
72
                    if (!Util\File::getFS()->isAbsolutePath($configFile)) {
73
                        $this->configFiles[$configFile] = realpath(Util::joinFile($this->getPath(), $configFile));
74
                    }
75
                }
76
                $this->configFiles = array_unique($this->configFiles);
77
            }
78
            // checks file(s)
79
            foreach ($this->configFiles as $fileName => $filePath) {
80
                if ($filePath === false || !file_exists($filePath)) {
81
                    unset($this->configFiles[$fileName]);
82
                    $this->getBuilder()->getLogger()->error(sprintf('Could not find configuration file "%s".', $fileName));
83
                }
84
            }
85
        }
86
        if ($this->getName() == 'new:site') {
87
            Util\File::getFS()->mkdir($this->getPath(true));
88
        }
89
90
        parent::initialize($input, $output);
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function run(InputInterface $input, OutputInterface $output): int
97
    {
98
        // disable debug mode if a verbosity level is specified
99
        if ($output->getVerbosity() != OutputInterface::VERBOSITY_NORMAL) {
100
            putenv('CECIL_DEBUG=false');
101
        }
102
        // force verbosity level to "debug" in debug mode
103
        if (getenv('CECIL_DEBUG') == 'true') {
104
            $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
105
        }
106
        if ($output->isDebug()) {
107
            // set env. variable in debug mode
108
            putenv('CECIL_DEBUG=true');
109
110
            return parent::run($input, $output);
111
        }
112
        // simplified error message
113
        try {
114
            return parent::run($input, $output);
115
        } catch (\Exception $e) {
116
            if ($this->io === null) {
117
                $this->io = new SymfonyStyle($input, $output);
118
            }
119
            $this->io->error($e->getMessage());
120
121
            exit(1);
122
        }
123
    }
124
125
    /**
126
     * Returns the working path.
127
     */
128
    protected function getPath(bool $create = false): ?string
129
    {
130
        if ($this->path === null) {
131
            try {
132
                // get working directory by default
133
                if (false === $this->path = getcwd()) {
134
                    throw new \Exception('Can\'t get current working directory.');
135
                }
136
                // ... or path
137
                if ($this->input->getArgument('path') !== null) {
138
                    $this->path = Path::canonicalize($this->input->getArgument('path'));
139
                }
140
                // try to get canonicalized absolute path
141
                if (!$create) {
142
                    if (realpath($this->path) === false) {
143
                        throw new \Exception(sprintf('The given path "%s" is not valid.', $this->path));
144
                    }
145
                    $this->path = realpath($this->path);
146
                }
147
            } catch (\Exception $e) {
148
                throw new \Exception($e->getMessage());
149
            }
150
        }
151
152
        return $this->path;
153
    }
154
155
    /**
156
     * Returns the configuration file name or path, if file exists, otherwise default name or false.
157
     */
158
    protected function findConfigFile(string $nameOrPath): string|false
159
    {
160
        $config = [
161
            'name' => self::CONFIG_FILE[0],
162
            'path' => false,
163
        ];
164
        foreach (self::CONFIG_FILE as $configFileName) {
165
            if (($configFilePath = realpath(Util::joinFile($this->getPath(), $configFileName))) !== false) {
166
                $config = [
167
                    'name' => $configFileName,
168
                    'path' => $configFilePath,
169
                ];
170
            }
171
        }
172
173
        return $config[$nameOrPath];
174
    }
175
176
    /**
177
     * Returns config file(s) path.
178
     */
179
    protected function getConfigFiles(): ?array
180
    {
181
        return $this->configFiles;
182
    }
183
184
    /**
185
     * Creates or returns a Builder instance.
186
     *
187
     * @throws RuntimeException
188
     */
189
    protected function getBuilder(array $config = []): Builder
190
    {
191
        try {
192
            // config
193
            if ($this->config === null) {
194
                $siteConfig = [];
195
                foreach ($this->getConfigFiles() as $fileName => $filePath) {
196
                    if ($filePath === false || false === $configContent = Util\File::fileGetContents($filePath)) {
197
                        throw new RuntimeException(sprintf('Can\'t read configuration file "%s".', $fileName));
198
                    }
199
                    $siteConfig = array_replace_recursive($siteConfig, Yaml::parse($configContent));
200
                }
201
                $this->config = array_replace_recursive($siteConfig, $config);
202
            }
203
            // builder
204
            if ($this->builder === null) {
205
                $this->builder = (new Builder($this->config, new ConsoleLogger($this->output)))
206
                    ->setSourceDir($this->getPath())
207
                    ->setDestinationDir($this->getPath());
208
            }
209
        } catch (ParseException $e) {
210
            throw new RuntimeException(sprintf('Configuration parsing error: %s', $e->getMessage()));
211
        } catch (\Exception $e) {
212
            throw new RuntimeException($e->getMessage());
213
        }
214
215
        return $this->builder;
216
    }
217
218
    /**
219
     * Opens path with editor.
220
     *
221
     * @throws RuntimeException
222
     */
223
    protected function openEditor(string $path, string $editor): void
224
    {
225
        $command = sprintf('%s "%s"', $editor, $path);
226
        switch (Util\Plateform::getOS()) {
227
            case Util\Plateform::OS_WIN:
228
                $command = sprintf('start /B "" %s "%s"', $editor, $path);
229
                break;
230
            case Util\Plateform::OS_OSX:
231
                // Typora on macOS
232
                if ($editor == 'typora') {
233
                    $command = sprintf('open -a typora "%s"', $path);
234
                }
235
                break;
236
        }
237
        $process = Process::fromShellCommandline($command);
238
        $process->run();
239
        if (!$process->isSuccessful()) {
240
            throw new RuntimeException(sprintf('Can\'t use "%s" editor.', $editor));
241
        }
242
    }
243
}
244