Skip to content

Commit fec18b2

Browse files
committed
Add MergeConfigs command
1 parent 6456a7d commit fec18b2

File tree

10 files changed

+351
-0
lines changed

10 files changed

+351
-0
lines changed

phpunit.xml.dist

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<env name="DB_USERNAME" value="root"/>
2222
<env name="DB_PASSWORD" value="password"/>
2323
<env name="DB_SCHEMA" value="framework-tests"/>
24+
<const name="IS_IN_PHPUNIT" value="true"/>
2425
</php>
2526
<source>
2627
<include>

src/MergeConfigs.php

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace Framework\CLI\Commands;
11+
12+
use Framework\CLI\CLI;
13+
use Framework\CLI\Command;
14+
use Framework\Helpers\Isolation;
15+
use Framework\MVC\App;
16+
use RuntimeException;
17+
18+
/**
19+
* Class MergeConfigs.
20+
*
21+
* @package dev-commands
22+
*/
23+
class MergeConfigs extends Command
24+
{
25+
protected string $description = 'Merge configuration files.';
26+
protected string $group = 'Configs';
27+
protected array $options = [
28+
'--extension' => 'Set a custom file extension. Default is ".php"',
29+
'-i' => 'Ignore files that do not return an array in the correct format.',
30+
];
31+
protected string $usage = 'mergeconfigs [options] -- [directory]';
32+
33+
protected function getDirectory() : string
34+
{
35+
$dir = $this->console->getArgument(0);
36+
if ($dir) {
37+
return $this->validateDir($dir);
38+
}
39+
$options = [];
40+
foreach (['config', 'configs'] as $option) {
41+
$dir = \getcwd() . '/' . $option;
42+
if (\is_dir($dir)) {
43+
$options[] = $option;
44+
}
45+
}
46+
$dir = \defined('IS_IN_PHPUNIT') && IS_IN_PHPUNIT
47+
? __DIR__ . '/../tests/configs/ok'
48+
: CLI::prompt('Config directory', $options);
49+
return $this->validateDir($dir);
50+
}
51+
52+
protected function validateDir(string $dir) : string
53+
{
54+
if (!\is_dir($dir)) {
55+
throw new RuntimeException('Config directory "' . $dir . '" does not exist');
56+
}
57+
return $dir;
58+
}
59+
60+
protected function getExtension() : string
61+
{
62+
$extension = $this->console->getOption('extension');
63+
if ($extension === true || $extension === null) {
64+
return '.php';
65+
}
66+
return $extension; // @phpstan-ignore-line;
67+
}
68+
69+
public function run() : void
70+
{
71+
$dir = $this->getDirectory();
72+
$extension = $this->getExtension();
73+
$config = [];
74+
$files = App::locator()->listFiles($dir);
75+
foreach ($files as $file) {
76+
if (!\str_ends_with($file, $extension)) {
77+
continue;
78+
}
79+
\ob_start();
80+
$contents = Isolation::require($file);
81+
\ob_end_clean();
82+
if (!\is_array($contents)) {
83+
if ($this->allowIgnore()) {
84+
continue;
85+
}
86+
throw new RuntimeException(
87+
'Config file "' . $file . '" does not return an array'
88+
);
89+
}
90+
if (empty($contents)) {
91+
if ($this->allowIgnore()) {
92+
continue;
93+
}
94+
throw new RuntimeException(
95+
'Config file "' . $file . '" return an empty array'
96+
);
97+
}
98+
foreach ($contents as $key => $values) {
99+
if (!\is_string($key)) {
100+
if ($this->allowIgnore()) {
101+
continue 2;
102+
}
103+
throw new RuntimeException(
104+
'Config file "' . $file . '" return invalid keys (must be strings)'
105+
);
106+
}
107+
if (!\is_array($values)) {
108+
if ($this->allowIgnore()) {
109+
continue 2;
110+
}
111+
throw new RuntimeException(
112+
'Config file "' . $file . '" return invalid values (must be arrays)'
113+
);
114+
}
115+
}
116+
$service = \substr($file, \strrpos($file, \DIRECTORY_SEPARATOR) + 1);
117+
$service = \substr($service, 0, -\strlen($extension));
118+
$config[$service] = $contents;
119+
}
120+
$config = \var_export($config, true);
121+
CLI::write('<?php');
122+
CLI::write('// Do not edit this file. It is created automatically.');
123+
CLI::write('// Created at: ' . \gmdate('Y-m-d H:i:s') . ' UTC');
124+
CLI::write('return ' . $config . ';');
125+
}
126+
127+
public function allowIgnore() : bool
128+
{
129+
return (bool) $this->console->getOption('i');
130+
}
131+
}

tests/MergeConfigsTest.php

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace Tests\CLI\Commands;
11+
12+
use Framework\Config\Config;
13+
use Framework\Testing\TestCase;
14+
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
15+
16+
#[RunTestsInSeparateProcesses]
17+
final class MergeConfigsTest extends TestCase
18+
{
19+
protected function prepareDefaults() : void
20+
{
21+
$this->config = new Config([
22+
'console' => [
23+
'default' => [
24+
'directories' => [
25+
__DIR__ . '/../src',
26+
],
27+
],
28+
],
29+
]);
30+
parent::prepareDefaults();
31+
}
32+
33+
protected function makeDir(string $relativePath) : string
34+
{
35+
$dir = __DIR__ . \DIRECTORY_SEPARATOR . $relativePath;
36+
return \str_replace(' ', '\ ', $dir);
37+
}
38+
39+
protected function runCli(string $relativePath, string $append = '') : void
40+
{
41+
$dir = $this->makeDir($relativePath);
42+
$append = $append !== '' ? ' ' . $append : '';
43+
$this->app->runCli('mergeconfigs ' . $dir . $append);
44+
}
45+
46+
public function testNoDir() : void
47+
{
48+
$this->app->runCli('mergeconfigs');
49+
self::assertStdoutContains('<?php');
50+
}
51+
52+
public function testCustomExtension() : void
53+
{
54+
$this->app->runCli('mergeconfigs --extension=.php');
55+
self::assertStdoutContains('<?php');
56+
}
57+
58+
public function testInvalidDir() : void
59+
{
60+
$this->expectException(\RuntimeException::class);
61+
$path = $this->makeDir('foo');
62+
$this->expectExceptionMessage('Config directory "' . $path . '" does not exist');
63+
$this->runCli('foo');
64+
}
65+
66+
public function testEmptyDir() : void
67+
{
68+
$this->runCli('configs/empty-dir');
69+
self::assertStdoutContains('<?php');
70+
self::assertStdoutContains('// Do not edit this file. It is created automatically.');
71+
self::assertStdoutContains('return array (');
72+
self::assertStdoutContains(');');
73+
self::assertStdoutNotContains("'bar' =>");
74+
}
75+
76+
public function testOk() : void
77+
{
78+
$this->runCli('configs/ok');
79+
self::assertStdoutContains('<?php');
80+
self::assertStdoutContains('// Do not edit this file. It is created automatically.');
81+
self::assertStdoutContains('return array (');
82+
self::assertStdoutContains("'bar' =>");
83+
self::assertStdoutContains("'key' => 'value'");
84+
self::assertStdoutContains("'other' =>");
85+
self::assertStdoutContains("'foo' => 'baz'");
86+
self::assertStdoutContains(');');
87+
}
88+
89+
public function testNotArray() : void
90+
{
91+
$this->expectException(\RuntimeException::class);
92+
$path = \realpath(__DIR__ . '/configs/not-array/foo.php');
93+
$this->expectExceptionMessage('Config file "' . $path . '" does not return an array');
94+
$this->runCli('configs/not-array');
95+
}
96+
97+
public function testNotArrayWithIgnore() : void
98+
{
99+
$this->runCli('configs/not-array', '-i');
100+
self::assertStdoutContains('<?php');
101+
}
102+
103+
public function testEmptyArray() : void
104+
{
105+
$this->expectException(\RuntimeException::class);
106+
$path = \realpath(__DIR__ . '/configs/empty-array/foo.php');
107+
$this->expectExceptionMessage('Config file "' . $path . '" return an empty array');
108+
$this->runCli('configs/empty-array');
109+
}
110+
111+
public function testEmptyArrayWithIgnore() : void
112+
{
113+
$this->runCli('configs/empty-array', '-i');
114+
self::assertStdoutContains('<?php');
115+
}
116+
117+
public function testInvalidKeys() : void
118+
{
119+
$this->expectException(\RuntimeException::class);
120+
$path = \realpath(__DIR__ . '/configs/invalid-keys/foo.php');
121+
$this->expectExceptionMessage('Config file "' . $path . '" return invalid keys (must be strings)');
122+
$this->runCli('configs/invalid-keys');
123+
}
124+
125+
public function testInvalidKeysWithIgnore() : void
126+
{
127+
$this->runCli('configs/invalid-keys', '-i');
128+
self::assertStdoutContains('<?php');
129+
}
130+
131+
public function testInvalidValues() : void
132+
{
133+
$this->expectException(\RuntimeException::class);
134+
$path = \realpath(__DIR__ . '/configs/invalid-values/foo.php');
135+
$this->expectExceptionMessage('Config file "' . $path . '" return invalid values (must be arrays)');
136+
$this->runCli('configs/invalid-values');
137+
}
138+
139+
public function testInvalidValuesWithIgnore() : void
140+
{
141+
$this->runCli('configs/invalid-values', '-i');
142+
self::assertStdoutContains('<?php');
143+
}
144+
}

tests/configs/empty-array/foo.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
return [];

tests/configs/empty-dir/.gitkeep

Whitespace-only changes.

tests/configs/invalid-keys/foo.php

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
return [
11+
1 => [],
12+
3 => [],
13+
];

tests/configs/invalid-values/foo.php

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
return [
11+
'foo' => 'bar',
12+
];

tests/configs/not-array/foo.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/

tests/configs/ok/bar.php

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
return [
11+
'default' => [
12+
'key' => 'value',
13+
],
14+
];

tests/configs/ok/foo.php

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
/*
3+
* This file is part of Aplus Framework Dev Commands Library.
4+
*
5+
* (c) Natan Felles <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
return [
11+
'default' => [
12+
'key' => 'value',
13+
],
14+
'other' => [
15+
'foo' => 'baz',
16+
],
17+
];

0 commit comments

Comments
 (0)