Compare commits

..

2 commits

11 changed files with 229 additions and 937 deletions

276
bin/cfg
View file

@ -22,12 +22,7 @@ foreach ($autoloadFiles as $autoloadFile) {
} }
} }
function verboseLog(bool $enabled, string $message): void $version = '0.3';
{
if ($enabled) fwrite(STDERR, "[verbose] $message".PHP_EOL);
}
$version = '0.4';
$actions = [ 'show', 'write', 'help' ]; $actions = [ 'show', 'write', 'help' ];
$settings = ['key' => '', 'value' => '']; $settings = ['key' => '', 'value' => ''];
@ -40,55 +35,15 @@ $collection = (new Input\InputCollection())
->description('Display help text') ->description('Display help text')
) // }}} ) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('verbose')->short('v') // {{{ ->add( Input\InputTypeFactory::build('LongOption')->name('in')->short('i') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL) ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
->description('Print debug details to STDERR') ->description('Path to a json data file to to read')
) // }}} ) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('in')->short('i') // {{{ ->add( Input\InputTypeFactory::build('LongOption')->name('out')->short('o') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
->description('Path to a JSON data file to read (for write)') ->description('Path to a json file to to write to')
->validator(new Input\Validator( ) // }}}
function (AbstractInputType $input, AbstractInputHandler $context)
{
$path = $context->find('in');
if ($path === null || $path === '') {
return null;
}
if (!is_readable($path))
{
throw new \Exception("Input file is not readable: $path");
}
$content = file_get_contents($path);
if ($content === false)
{
throw new \Exception("Can not read input file: $path");
}
try
{
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
}
catch (\JsonException $ex)
{
throw new \Exception(sprintf(
'Input JSON is invalid (%s): %s',
(string)$ex->getCode(),
$ex->getMessage()
));
}
if (!is_array($data))
{
throw new \Exception('Input JSON must decode to an object/array');
}
return $data;
}
))
) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('mode')->short('m') // {{{ ->add( Input\InputTypeFactory::build('LongOption')->name('mode')->short('m') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
@ -105,60 +60,6 @@ $collection = (new Input\InputCollection())
->description('Path where the config/ directory of the package conf files is located, defaults to the working dir') ->description('Path where the config/ directory of the package conf files is located, defaults to the working dir')
) // }}} ) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('siteDir')->short('s') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
->description('Site/instance directory below config/. Accepts owner_xyz or config/owner_xyz')
->validator(new Input\Validator(
function (AbstractInputType $input, AbstractInputHandler $context)
{
$siteInput = $context->find('siteDir');
if ($siteInput === null || $siteInput === false) {
return null;
}
$siteInput = trim((string)$siteInput);
$appPath = $context->find('appPath');
if (!$appPath) {
$appPath = getcwd().'/';
}
$appPath = rtrim($appPath, '/').'/';
// Accept both "owner_xyz" and "config/owner_xyz" and normalize to site key.
if (str_starts_with($siteInput, $appPath.'config/'))
{
$siteInput = substr($siteInput, strlen($appPath.'config/'));
}
elseif (str_starts_with($siteInput, 'config/'))
{
$siteInput = substr($siteInput, strlen('config/'));
}
$siteInput = trim($siteInput, '/');
if ($siteInput === '')
{
throw new \Exception('Option --siteDir is empty.');
}
// Block directory traversal and hidden-dot segments.
if (str_contains($siteInput, '..') || preg_match('~(^|/)\.(?:/|$)~', $siteInput))
{
throw new \Exception("Invalid directory in --siteDir: '$siteInput'.");
}
// Allow only predictable path segments for instance directories.
foreach (explode('/', $siteInput) as $part)
{
if ($part === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $part))
{
throw new \Exception("Invalid directory name segment '$part' in --siteDir.");
}
}
return $siteInput;
}
))
) // }}}
->add( Input\InputTypeFactory::build('Argument')->name('action') // {{{ ->add( Input\InputTypeFactory::build('Argument')->name('action') // {{{
->flags(AbstractInputType::FLAG_REQUIRED) ->flags(AbstractInputType::FLAG_REQUIRED)
->description( ->description(
@ -201,30 +102,18 @@ $collection = (new Input\InputCollection())
->description( ->description(
'the settings you want to work on. With action "write" you can pass the value to set as JSON after an equal sign.' 'the settings you want to work on. With action "write" you can pass the value to set as JSON after an equal sign.'
) )
->validator(new Input\Validator( ->validator(new Input\Validator(
function (AbstractInputType $input, AbstractInputHandler $context) function (AbstractInputType $input, AbstractInputHandler $context)
{ {
$setting = $context->find('setting'); $setting = $context->find('setting');
$action = $context->find('action'); $action = $context->find('action');
$inputPayload = $context->find('in');
if ($action === 'write')
{
if (($setting !== null && $setting !== '') && $inputPayload) {
throw new \Exception('Please use either SETTING or --in, not both.');
}
}
if ($setting === null || $setting === '') {
return ['key' => '', 'value' => null];
}
$setting = explode('=', $setting); $setting = explode('=', $setting);
$settings['key'] = $setting[0]; $settings['key'] = $setting[0];
if ($action === 'write') if ($action === 'write')
{ {
$value = $setting[1] ?? null; $value = $setting[1];
if (! (isset($value) || $context->find('in'))) { if (! (isset($value) || $context->find('data'))) {
throw new \Exception('You need a value to write'); throw new \Exception('You need a value to write');
} }
$specialValues = [ 'true', 'false', 'null' ]; $specialValues = [ 'true', 'false', 'null' ];
@ -250,50 +139,18 @@ $collection = (new Input\InputCollection())
$usage = Cli\manpage( basename(__FILE__), $version, $usage = Cli\manpage( basename(__FILE__), $version,
'read write settings', 'read write settings',
$collection, Colour::FG_GREEN, Colour::FG_WHITE, $collection, Colour::FG_GREEN, Colour::FG_WHITE,
[ [
'Examples' => 'Examples' =>
'Basic usage:' 'cfg show VeruA db:host'.PHP_EOL.
.PHP_EOL 'cfg write VeruA \'db:host="newHost"\''.PHP_EOL
.'cfg show VeruA db:host' ]
.PHP_EOL
.'cfg write VeruA \'db:host="newHost"\''
.PHP_EOL
.PHP_EOL
.'Advance usage:'
.PHP_EOL
.'cfg write prefix \'module:enabled=true\''
.PHP_EOL
.PHP_EOL
.'#Site specific write:'
.PHP_EOL
.'cfg write prefix \'module:enabled=true\' --siteDir=owner_xyz'
.PHP_EOL
.PHP_EOL
.'#Batch write from JSON file:'
.PHP_EOL
.'cfg write prefix --siteDir=owner_xyz -i /tmp/extension.json'
.PHP_EOL
.PHP_EOL
.'#Batch write from JSON string:'
.PHP_EOL
.'cfg write prefix \'module={"enabled":true,"timeout":30,"label":"example"}\' --siteDir=owner_xyz'
.PHP_EOL
.PHP_EOL
.'#Read merged value for a site:'
.PHP_EOL
.'cfg show prefix module:enabled --siteDir=owner_xyz'
.PHP_EOL
]
).PHP_EOL; ).PHP_EOL;
// Get the supplied input. Passing the collection will make the handler bind values // Get the supplied input. Passing the collection will make the handler bind values
// and validate the input according to our collection // and validate the input according to our collection
try try {
{
$argv = Input\InputHandlerFactory::build('Argv', $collection); $argv = Input\InputHandlerFactory::build('Argv', $collection);
} } catch (\Exception $ex) {
catch (\Exception $ex)
{
echo $usage; echo $usage;
if (isset($argv[1])) { if (isset($argv[1])) {
@ -315,41 +172,42 @@ if ($argv->find( 'help' ) || $argv->find('action') == 'help')
$prefix = $argv->find('prefix'); $prefix = $argv->find('prefix');
$verbose = (bool)$argv->find('verbose'); /*
echo $argv->find('action').PHP_EOL;
echo ($prefix).PHP_EOL;
echo $argv->find('setting')['key'].PHP_EOL;
echo $argv->find('setting')['value'].PHP_EOL;
*/
// var_dump($cfg);
$appPath = $argv->find('appPath'); $appPath = $argv->find('appPath');
if (!$appPath) $appPath = getcwd().'/'; if (!$appPath) $appPath = getcwd().'/';
$appPath = rtrim($appPath, '/').'/';
/* $it = new RecursiveDirectoryIterator($appPath);
foreach(new RecursiveIteratorIterator($it) as $file)
{
$configDir = $file->getPath();
if ($file->isDir() && $file->getFilename() == '.' && basename($configDir) == 'config') {
echo "found config dir: $configDir\n";
}
}
*/
$mode = ($argv->find('mode') == '') ? null : $argv->find('mode'); $mode = ($argv->find('mode') == '') ? null : $argv->find('mode');
$cfg = (new Settings([], $mode))->appPath($appPath)->prefix($prefix); $cfg = (new Settings([], $mode))->appPath($appPath)->prefix($prefix);
// pkgPath points to package defaults (e.g. <prefix>.default.conf.php) if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath($pkgPath);
if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath(rtrim($pkgPath, '/').'/');
$site = null; try {
$siteFlag = 0x01;
$site = $argv->find('siteDir');
if ($site !== null && $site !== false)
{
// Reuse util-settings site resolution: config/<site>/<prefix>.conf.php
$cfg->site($site);
verboseLog($verbose, "siteDir resolved to '$site'");
}
try
{
if (is_readable($cfg->buildFileName('default'))) { if (is_readable($cfg->buildFileName('default'))) {
$cfg->load(); $cfg->load();
} }
elseif (is_readable($cfgFile = $cfg->buildFileName())) { elseif (is_readable($cfgFile = $cfg->buildFileName())) {
$cfg->load(require($cfgFile)); $cfg->load(require($cfgFile));
} }
} } catch (Exception $e) {
catch (\Throwable $e)
{
fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL); fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL);
exit(1); exit(1);
} }
verboseLog($verbose, 'config bootstrap loaded'); //var_dump($cfg);
$result = $cfg; $result = $cfg;
$settings = $argv->find('setting') ?? $settings; $settings = $argv->find('setting') ?? $settings;
@ -374,63 +232,33 @@ if ($result instanceof Settings) $result = $result->toArray();
switch ($argv->find('action')) switch ($argv->find('action'))
{ {
case 'show': case 'show':
verboseLog($verbose, "show action for prefix '$prefix'");
$out = (is_string($result)) ? $result : json_encode($result, JSON_PRETTY_PRINT); $out = (is_string($result)) ? $result : json_encode($result, JSON_PRETTY_PRINT);
echo $out.PHP_EOL; echo $out.PHP_EOL;
break; break;
case 'write': case 'write':
$inputPayload = $argv->find('in');
if (!$inputPayload && $settings['key'] === '')
{
fwrite(STDERR, 'Nothing to write: provide SETTING or --in.'.PHP_EOL);
exit(1);
}
$path = ($settings['key'] !== '') ? explode(':', $settings['key']) : []; $path = ($settings['key'] !== '') ? explode(':', $settings['key']) : [];
verboseLog($verbose, 'write source: '.($inputPayload ? '--in' : 'SETTING')); var_dump($path);
$setting2write = $settings['value'];
if ($inputPayload)
{
$setting2write = $inputPayload;
}
else
{
$setting2write = $settings['value'];
}
verboseLog($verbose, 'write path: '.json_encode($path));
while ( ! empty($path)) while ( ! empty($path))
{ {
$setting2write = [array_pop($path) => $setting2write]; $setting2write = [array_pop($path) => $setting2write];
} }
if (is_readable($file = $cfg->buildFileName()))
$writeType = ($site !== null && $site !== false) ? $siteFlag : null;
$file = $cfg->buildFileName($writeType);
verboseLog($verbose, "write target: $file");
if (is_readable($file))
{ {
$setting2write = array_replace_recursive(require($file), $setting2write); $setting2write = array_replace_recursive(require($file), $setting2write);
copy($file, "$file.bak"); copy($file, "$file.bak");
verboseLog($verbose, "existing config merged from: $file");
} }
$targetDir = dirname($file);
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true) && !is_dir($targetDir))
{
fwrite(STDERR, "Can not create directory: $targetDir".PHP_EOL);
exit(1);
}
$writeCfg = $cfg->create($setting2write); $writeCfg = $cfg->create($setting2write);
verboseLog($verbose, 'payload prepared for writer'); // var_dump($writeCfg->toArray());
try try {
{ (new SettingsWriter($writeCfg))->write();
(new SettingsWriter($writeCfg, '', $writeType))->write();
echo "Written modified settings to: $file".PHP_EOL; echo "Written modified settings to: $file".PHP_EOL;
} }
catch (\Exception $e) { catch (\Exception $e) {
fwrite(STDERR, $e->getMessage().PHP_EOL); echo $e->getMessage().PHP_EOL;
exit(1);
} }
break; break;

View file

@ -1,7 +1,6 @@
{ {
"name": "rabe/util-settings", "name": "rabe/util-settings",
"description": "Package for reading and writing configuration settings", "description": "Package for reading and writing configuration settings",
"bin": ["bin/cfg"],
"license": "AGPL3", "license": "AGPL3",
"authors": [ "authors": [
{ {

265
composer.lock generated
View file

@ -498,30 +498,30 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "doctrine/instantiator", "name": "doctrine/instantiator",
"version": "2.0.0", "version": "1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/instantiator.git", "url": "https://github.com/doctrine/instantiator.git",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^8.1" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^11", "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*", "ext-pdo": "*",
"ext-phar": "*", "ext-phar": "*",
"phpbench/phpbench": "^1.2", "phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.9.4", "phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1.3", "phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^9.5.27", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^5.4" "vimeo/psalm": "^4.30 || ^5.4"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -548,7 +548,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/instantiator/issues", "issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/2.0.0" "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
}, },
"funding": [ "funding": [
{ {
@ -564,20 +564,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-12-30T00:23:10+00:00" "time": "2022-12-30T00:15:36+00:00"
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.12.1", "version": "1.11.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -585,12 +585,11 @@
}, },
"conflict": { "conflict": {
"doctrine/collections": "<1.6.8", "doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3 <3.2.2" "doctrine/common": "<2.13.3 || >=3,<3.2.2"
}, },
"require-dev": { "require-dev": {
"doctrine/collections": "^1.6.8", "doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2", "doctrine/common": "^2.13.3 || ^3.2.2",
"phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
}, },
"type": "library", "type": "library",
@ -616,7 +615,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/myclabs/DeepCopy/issues", "issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
}, },
"funding": [ "funding": [
{ {
@ -624,31 +623,29 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-11-08T17:47:46+00:00" "time": "2023-03-08T13:26:56+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v5.4.0", "version": "v4.15.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494" "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494", "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"php": ">=7.4" "php": ">=7.0"
}, },
"require-dev": { "require-dev": {
"ircmaxell/php-yacc": "^0.0.7", "ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^9.0" "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
}, },
"bin": [ "bin": [
"bin/php-parse" "bin/php-parse"
@ -656,7 +653,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "5.0-dev" "dev-master": "4.9-dev"
} }
}, },
"autoload": { "autoload": {
@ -680,27 +677,26 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
}, },
"time": "2024-12-30T11:07:19+00:00" "time": "2023-03-05T19:49:14+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
"version": "2.0.4", "version": "2.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phar-io/manifest.git", "url": "https://github.com/phar-io/manifest.git",
"reference": "54750ef60c58e43759730615a392c31c80e23176" "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
"reference": "54750ef60c58e43759730615a392c31c80e23176", "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*",
"ext-phar": "*", "ext-phar": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"phar-io/version": "^3.0.1", "phar-io/version": "^3.0.1",
@ -741,15 +737,9 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": { "support": {
"issues": "https://github.com/phar-io/manifest/issues", "issues": "https://github.com/phar-io/manifest/issues",
"source": "https://github.com/phar-io/manifest/tree/2.0.4" "source": "https://github.com/phar-io/manifest/tree/2.0.3"
}, },
"funding": [ "time": "2021-07-20T11:28:43+00:00"
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2024-03-03T12:33:53+00:00"
}, },
{ {
"name": "phar-io/version", "name": "phar-io/version",
@ -804,35 +794,35 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "9.2.32", "version": "9.2.26",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"nikic/php-parser": "^4.19.1 || ^5.1.0", "nikic/php-parser": "^4.15",
"php": ">=7.3", "php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.6", "phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.4", "phpunit/php-text-template": "^2.0.2",
"sebastian/code-unit-reverse-lookup": "^2.0.3", "sebastian/code-unit-reverse-lookup": "^2.0.2",
"sebastian/complexity": "^2.0.3", "sebastian/complexity": "^2.0",
"sebastian/environment": "^5.1.5", "sebastian/environment": "^5.1.2",
"sebastian/lines-of-code": "^1.0.4", "sebastian/lines-of-code": "^1.0.3",
"sebastian/version": "^3.0.2", "sebastian/version": "^3.0.1",
"theseer/tokenizer": "^1.2.3" "theseer/tokenizer": "^1.2.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.6" "phpunit/phpunit": "^9.3"
}, },
"suggest": { "suggest": {
"ext-pcov": "PHP extension that provides line coverage", "ext-pcov": "PHP extension that provides line coverage",
@ -841,7 +831,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "9.2.x-dev" "dev-master": "9.2-dev"
} }
}, },
"autoload": { "autoload": {
@ -869,8 +859,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32"
}, },
"funding": [ "funding": [
{ {
@ -878,7 +867,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-08-22T04:23:01+00:00" "time": "2023-03-06T12:58:08+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -1123,45 +1112,45 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.6.22", "version": "9.6.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5",
"reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.5.0 || ^2", "doctrine/instantiator": "^1.3.1 || ^2",
"ext-dom": "*", "ext-dom": "*",
"ext-json": "*", "ext-json": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-xml": "*", "ext-xml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.12.1", "myclabs/deep-copy": "^1.10.1",
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.2.1", "phar-io/version": "^3.0.2",
"php": ">=7.3", "php": ">=7.3",
"phpunit/php-code-coverage": "^9.2.32", "phpunit/php-code-coverage": "^9.2.13",
"phpunit/php-file-iterator": "^3.0.6", "phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1", "phpunit/php-invoker": "^3.1.1",
"phpunit/php-text-template": "^2.0.4", "phpunit/php-text-template": "^2.0.3",
"phpunit/php-timer": "^5.0.3", "phpunit/php-timer": "^5.0.2",
"sebastian/cli-parser": "^1.0.2", "sebastian/cli-parser": "^1.0.1",
"sebastian/code-unit": "^1.0.8", "sebastian/code-unit": "^1.0.6",
"sebastian/comparator": "^4.0.8", "sebastian/comparator": "^4.0.8",
"sebastian/diff": "^4.0.6", "sebastian/diff": "^4.0.3",
"sebastian/environment": "^5.1.5", "sebastian/environment": "^5.1.3",
"sebastian/exporter": "^4.0.6", "sebastian/exporter": "^4.0.5",
"sebastian/global-state": "^5.0.7", "sebastian/global-state": "^5.0.1",
"sebastian/object-enumerator": "^4.0.4", "sebastian/object-enumerator": "^4.0.3",
"sebastian/resource-operations": "^3.0.4", "sebastian/resource-operations": "^3.0.3",
"sebastian/type": "^3.2.1", "sebastian/type": "^3.2",
"sebastian/version": "^3.0.2" "sebastian/version": "^3.0.2"
}, },
"suggest": { "suggest": {
@ -1205,8 +1194,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22"
}, },
"funding": [ "funding": [
{ {
@ -1222,20 +1210,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-05T13:48:26+00:00" "time": "2023-03-09T06:34:10+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
"version": "1.0.2", "version": "1.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git", "url": "https://github.com/sebastianbergmann/cli-parser.git",
"reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
"reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1270,7 +1258,7 @@
"homepage": "https://github.com/sebastianbergmann/cli-parser", "homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues", "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
}, },
"funding": [ "funding": [
{ {
@ -1278,7 +1266,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:27:43+00:00" "time": "2020-09-28T06:08:49+00:00"
}, },
{ {
"name": "sebastian/code-unit", "name": "sebastian/code-unit",
@ -1467,20 +1455,20 @@
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
"version": "2.0.3", "version": "2.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git", "url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.18 || ^5.0", "nikic/php-parser": "^4.7",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -1512,7 +1500,7 @@
"homepage": "https://github.com/sebastianbergmann/complexity", "homepage": "https://github.com/sebastianbergmann/complexity",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues", "issues": "https://github.com/sebastianbergmann/complexity/issues",
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
}, },
"funding": [ "funding": [
{ {
@ -1520,20 +1508,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-12-22T06:19:30+00:00" "time": "2020-10-26T15:52:27+00:00"
}, },
{ {
"name": "sebastian/diff", "name": "sebastian/diff",
"version": "4.0.6", "version": "4.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/diff.git", "url": "https://github.com/sebastianbergmann/diff.git",
"reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1578,7 +1566,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/diff/issues", "issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
}, },
"funding": [ "funding": [
{ {
@ -1586,7 +1574,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:30:58+00:00" "time": "2020-10-26T13:10:38+00:00"
}, },
{ {
"name": "sebastian/environment", "name": "sebastian/environment",
@ -1653,16 +1641,16 @@
}, },
{ {
"name": "sebastian/exporter", "name": "sebastian/exporter",
"version": "4.0.6", "version": "4.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git", "url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1718,7 +1706,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues", "issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
}, },
"funding": [ "funding": [
{ {
@ -1726,20 +1714,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:33:00+00:00" "time": "2022-09-14T06:03:37+00:00"
}, },
{ {
"name": "sebastian/global-state", "name": "sebastian/global-state",
"version": "5.0.7", "version": "5.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git", "url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1782,7 +1770,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues", "issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
}, },
"funding": [ "funding": [
{ {
@ -1790,24 +1778,24 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:35:11+00:00" "time": "2022-02-14T08:28:10+00:00"
}, },
{ {
"name": "sebastian/lines-of-code", "name": "sebastian/lines-of-code",
"version": "1.0.4", "version": "1.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git", "url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.18 || ^5.0", "nikic/php-parser": "^4.6",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -1839,7 +1827,7 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code", "homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
}, },
"funding": [ "funding": [
{ {
@ -1847,7 +1835,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-12-22T06:20:34+00:00" "time": "2020-11-28T06:42:11+00:00"
}, },
{ {
"name": "sebastian/object-enumerator", "name": "sebastian/object-enumerator",
@ -2026,16 +2014,16 @@
}, },
{ {
"name": "sebastian/resource-operations", "name": "sebastian/resource-operations",
"version": "3.0.4", "version": "3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git", "url": "https://github.com/sebastianbergmann/resource-operations.git",
"reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2047,7 +2035,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.0-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -2068,7 +2056,8 @@
"description": "Provides a list of PHP built-in functions that operate on resources", "description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations", "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": { "support": {
"source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
"source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
}, },
"funding": [ "funding": [
{ {
@ -2076,7 +2065,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-14T16:00:52+00:00" "time": "2020-09-28T06:45:17+00:00"
}, },
{ {
"name": "sebastian/type", "name": "sebastian/type",
@ -2189,16 +2178,16 @@
}, },
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",
"version": "1.2.3", "version": "1.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/theseer/tokenizer.git", "url": "https://github.com/theseer/tokenizer.git",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2227,7 +2216,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": { "support": {
"issues": "https://github.com/theseer/tokenizer/issues", "issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.3" "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
}, },
"funding": [ "funding": [
{ {
@ -2235,7 +2224,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-03T12:36:25+00:00" "time": "2021-07-28T10:34:58+00:00"
} }
], ],
"aliases": [ "aliases": [

View file

@ -29,7 +29,7 @@ namespace rabe\Util;
class Settings implements \Iterator, \Countable class Settings implements \Iterator, \Countable
{ {
private const SITE = 0x01; private const SITE = 0x01;
private array $settings = []; private array $settings = [];
private string $appPath = './'; private string $appPath = './';
@ -68,7 +68,7 @@ class Settings implements \Iterator, \Countable
public function appPath( ?string $path=null ) public function appPath( ?string $path=null )
{ {
if (! isset($path)) return $this->appPath; if (! isset($path)) return $this->appPath;
if ($this->pkgPath === $this->appPath) $this->pkgPath = $path; if ($this->pkgPath === $this->appPath) $this->pkgPath = $path;
$this->appPath = $path; $this->appPath = $path;
return $this; return $this;
@ -105,7 +105,7 @@ class Settings implements \Iterator, \Countable
$this->modes += $modes; $this->modes += $modes;
return $this; return $this;
}// }}} }// }}}
// site() {{{ // site() {{{
/** {{{ /** {{{
*///}}} *///}}}
@ -114,7 +114,7 @@ class Settings implements \Iterator, \Countable
$this->site = $site; $this->site = $site;
return $this; return $this;
}// }}} }// }}}
// load() method {{{ // load() method {{{
/** {{{ /** {{{
* Load the configuration from the files and merge them * Load the configuration from the files and merge them
@ -129,12 +129,12 @@ class Settings implements \Iterator, \Countable
} }
else else
{ {
$modes = $this->modes; $modes = $this->modes;
// load Prefix.default.conf.php // load Prefix.default.conf.php
$defaultConf = $this->buildFileName( array_shift($modes) ); $defaultConf = $this->buildFileName( array_shift($modes) );
if ( file_exists($defaultConf) ) if ( file_exists($defaultConf) )
{ {
$this->settings = require( $defaultConf ); $this->settings = require( $defaultConf );
@ -145,45 +145,34 @@ class Settings implements \Iterator, \Countable
throw new \Exception( "No settingsfile: $defaultConf" ); throw new \Exception( "No settingsfile: $defaultConf" );
// throw new FileNotFoundException(); // throw new FileNotFoundException();
} }
// add modes from the drfault config file (no modes from other files are added) // add modes from the drfault config file (no modes from other files are added)
if (isset($this->settings['modes'])) { if (isset($this->settings['modes'])) {
$this->addModes($this->settings['modes']); $this->addModes($this->settings['modes']);
} }
// load local config without merging - we need it here for the mode // load local config without merging - we need it here for the mode
$conf = $this->buildFileName(); $conf = $this->buildFileName();
$localConf = []; $localConf = false;
if (file_exists($conf)) $localConf = require($conf); if (file_exists($conf)) $localConf = require($conf);
// if a mode was set in the constructor do not overwrite it // if a mode was set in the constructor do not overwrite it
if (! isset($this->mode)) { if (! isset($this->mode)) {
// if a localConf Mode is set use it, or take the default conf mode // if a localConf Mode is set use it, or take the default conf mode
// if local/default config has no mode, fall back to the first known mode (normally "prod") $this->mode = (isset($localConf['mode'])) ? $localConf['mode'] : $this->settings['mode'];
if (isset($localConf['mode']) && is_string($localConf['mode']) && $localConf['mode'] !== '') {
$this->mode = $localConf['mode'];
}
elseif (isset($this->settings['mode']) && is_string($this->settings['mode']) && $this->settings['mode'] !== '') {
$this->mode = $this->settings['mode'];
}
else {
// Backward-compatible fallback for configs without explicit mode.
$this->mode = (string)array_key_first($this->modes);
$this->settings['mode'] = $this->mode;
}
} else { } else {
$this->settings['mode'] = $this->mode; $this->settings['mode'] = $this->mode;
} }
if ( isset($this->site) ) if ( isset($this->site) )
{ {
$siteConf = $this->buildFileName(self::SITE); $siteConf = $this->buildFileName(self::SITE);
if (file_exists($siteConf)) if (file_exists($siteConf))
{ {
$localConf = array_replace_recursive($localConf, require($siteConf)); $localConf = array_replace_recursive($this->settings, require($siteConf));
} }
} }
// load and merge Prefix.mode.conf.php // load and merge Prefix.mode.conf.php
if ( isset( $this->modes[ $this->mode ] ) ) if ( isset( $this->modes[ $this->mode ] ) )
{ {
@ -203,14 +192,14 @@ class Settings implements \Iterator, \Countable
// log invalid mode // log invalid mode
throw new \Exception( "The mode '{$this->mode}' is not valid" ); throw new \Exception( "The mode '{$this->mode}' is not valid" );
} }
// merge Prefix.conf.php (localConf) // merge Prefix.conf.php (localConf)
if ($localConf !== false) if ($localConf !== false)
{ {
$this->settings = array_replace_recursive( $this->settings, $localConf ); $this->settings = array_replace_recursive( $this->settings, $localConf );
} }
} }
// echo "<pre>"; print_r( $this->settings ); echo "</pre>"; // echo "<pre>"; print_r( $this->settings ); echo "</pre>";
return $this; return $this;
}// }}} }// }}}
@ -220,9 +209,9 @@ class Settings implements \Iterator, \Countable
*///}}} *///}}}
public function mergeFile( ) public function mergeFile( )
{ {
}// }}} }// }}}
// create() {{{ // create() {{{
/** {{{ /** {{{
* Creates a copy of the Settingsobject without the configuration directives * Creates a copy of the Settingsobject without the configuration directives
@ -244,15 +233,15 @@ class Settings implements \Iterator, \Countable
{ {
$mode = ''; $mode = '';
$path = $this->appPath.$this->confDir; $path = $this->appPath.$this->confDir;
if ($type === self::SITE) $path .= $this->site.'/'; if ($type === self::SITE) $path .= $this->site.'/';
if (is_string( $type ) &! empty( $type )) if (is_string( $type ) &! empty( $type ))
{ {
$mode = "$type."; $mode = "$type.";
$path = $this->pkgPath.$this->confDir; $path = $this->pkgPath.$this->confDir;
} }
return $path.$this->filePrefix.$mode.$this->filePostfix; return $path.$this->filePrefix.$mode.$this->filePostfix;
} }
// }}} // }}}
@ -281,7 +270,7 @@ class Settings implements \Iterator, \Countable
* Rewind the Iterator to the first element * Rewind the Iterator to the first element
* @link https://www.php.net/iterator.rewind * @link https://www.php.net/iterator.rewind
*/ */
public function rewind(): void public function rewind()
{ {
reset( $this->settings); reset( $this->settings);
} }
@ -290,7 +279,7 @@ class Settings implements \Iterator, \Countable
* Return the current element * Return the current element
* @link https://www.php.net/iterator.current * @link https://www.php.net/iterator.current
*/ */
public function current(): mixed public function current()
{ {
return current( $this->settings ); return current( $this->settings );
} }
@ -299,7 +288,7 @@ class Settings implements \Iterator, \Countable
* Return the key of the current element * Return the key of the current element
* @link https://www.php.net/iterator.key * @link https://www.php.net/iterator.key
*/ */
public function key(): mixed public function key()
{ {
return key( $this->settings ); return key( $this->settings );
} }
@ -308,16 +297,16 @@ class Settings implements \Iterator, \Countable
* Move forward to next element * Move forward to next element
* @link https://www.php.net/iterator.next * @link https://www.php.net/iterator.next
*/ */
public function next(): void public function next()
{ {
next( $this->settings ); return next( $this->settings );
} }
/** /**
* Checks if current position is valid * Checks if current position is valid
* @link https://www.php.net/iterator.valid * @link https://www.php.net/iterator.valid
*/ */
public function valid(): bool public function valid()
{ {
$key = key( $this->settings ); $key = key( $this->settings );
return ($key !== null && $key !== false); return ($key !== null && $key !== false);
@ -330,12 +319,12 @@ class Settings implements \Iterator, \Countable
* Count elements * Count elements
* @link https://www.php.net/countable.count.php * @link https://www.php.net/countable.count.php
*/ */
public function count(): int public function count()
{ {
return count($this->settings); count($this->settings);
} }
// }}} // }}}
// Magic getter/setter methods {{{ // Magic getter/setter methods {{{
public function __set( $name, $value ) public function __set( $name, $value )
{ {
@ -363,8 +352,17 @@ class Settings implements \Iterator, \Countable
{ {
unset( $this->settings[$name] ); unset( $this->settings[$name] );
} }
// }}}
public function __call(string $name, array $arguments)
{
$type = substr($name, 0, 3);
if ($type === 'get') {
$propertyName = lcfirst(substr($name , 3));
return (is_array( $this->settings[$propertyName] ))
? $this->create( $this->settings[$propertyName] )
: $this->settings[$propertyName]; }
}
} }
/* jEdit buffer local properties {{{ /* jEdit buffer local properties {{{

View file

@ -22,8 +22,6 @@
declare(strict_types=1); declare(strict_types=1);
namespace rabe\Util; namespace rabe\Util;
use ErrorException;
/** /**
* Write Settings into a File * Write Settings into a File
* @author Norbert.e.Wagner dev@norb.me * @author Norbert.e.Wagner dev@norb.me
@ -47,16 +45,13 @@ class SettingsWriter
* *
* @param Settings $settings an object of type settings * @param Settings $settings an object of type settings
* @param String $name The name midfix for the settings File * @param String $name The name midfix for the settings File
* @param String|int|null $type Optional type for Settings::buildFileName(), e.g. site flag */
*/ public function __construct( Settings $settings, $name='' )
public function __construct( Settings $settings, $name='', $type=null )
{ {
$this->settings = $settings; $this->settings = $settings;
$file = ( $type === null ) $file = $settings->buildFileName( $name );
? $settings->buildFileName( $name )
: $settings->buildFileName( $type );
if ( ! $this->handle = fopen( $file, 'w' ) ) if ( ! $this->handle = fopen( $file, 'w' ) )
{ {
throw new ErrorException( "Can not open File: $file" ); throw new ErrorException( "Can not open File: $file" );

View file

@ -1,457 +0,0 @@
<?php
declare(strict_types=1);
namespace rabe\Util\tests;
use PHPUnit\Framework\TestCase;
class CfgTest extends TestCase
{
private string $tmpDir;
/* Lifecycle {{{ */
protected function setUp(): void
{
$this->tmpDir = rtrim(sys_get_temp_dir(), '/').'/util-settings-cfg-'.bin2hex(random_bytes(8));
mkdir($this->tmpDir, 0775, true);
mkdir($this->tmpDir.'/config', 0775, true);
file_put_contents(
$this->tmpDir.'/config/Extension.default.conf.php',
"<?php return [\n\t'mode' => 'prod',\n\t'module' => [\n\t\t'code' => '',\n\t\t'label' => '',\n\t\t'flags' => [\n\t\t\t'enabled' => false,\n\t\t],\n\t],\n\t'feature' => [\n\t\t'endpoint' => '',\n\t],\n];\n"
);
}
protected function tearDown(): void
{
$this->removeDir($this->tmpDir);
}
/* }}} */
/* Write Flow {{{ */
public function testWriteWithoutDirectoryNameUsesLocalConfigFile(): void
{
$result = $this->runWrite([
'module:code="X100"',
]);
$this->assertCfgSuccess($result);
$localFile = $this->tmpDir.'/config/Extension.conf.php';
$this->assertFileExists($localFile);
$cfg = require $localFile;
$this->assertSame('X100', $cfg['module']['code']);
}
public function testWriteWithoutModeFallsBackToDefaultMode(): void
{
file_put_contents(
$this->tmpDir.'/config/Extension.default.conf.php',
"<?php return [\n\t'module' => [\n\t\t'code' => '',\n\t],\n];\n"
);
$result = $this->runWrite([
'module:code="X100"',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/Extension.conf.php');
}
public function testWriteWithInputFileWritesMultipleSettings(): void
{
$inFile = $this->createJsonInputFile([
'module' => [
'code' => 'X100',
'label' => 'demo-module',
],
'feature' => [
'endpoint' => 'https://example.invalid/v1/resource',
],
]);
$result = $this->runWrite([
'--siteDir=owner_xyz',
'-i',
$inFile,
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
$this->assertSame('demo-module', $cfg['module']['label']);
$this->assertSame('https://example.invalid/v1/resource', $cfg['feature']['endpoint']);
}
public function testWriteAcceptsJsonStringValueInSetting(): void
{
$result = $this->runWrite([
'module={"code":"X100","label":"demo-module"}',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
$this->assertSame('demo-module', $cfg['module']['label']);
}
public function testWriteSupportsNestedPathWithColonNotation(): void
{
$result = $this->runWrite([
'module:flags:enabled=true',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertTrue($cfg['module']['flags']['enabled']);
}
public function testWriteCoercesSimpleStringValueToJsonString(): void
{
$result = $this->runWrite([
'module:code=X100',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
}
public function testWriteRejectsInvalidJsonStringValue(): void
{
$result = $this->runWrite([
'module={"code":',
'--siteDir=owner_xyz',
]);
$this->assertCfgFailure($result, 'The value does not appear to be a valid JSON string.');
}
public function testWriteWithSiteDirCreatesAndWritesSiteConfig(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
$this->assertFileExists($siteFile);
$this->assertFileDoesNotExist($this->tmpDir.'/config/Extension.conf.php');
$cfg = require $siteFile;
$this->assertSame('X100', $cfg['module']['code']);
}
public function testWriteWithSiteDirMergesIntoExistingSiteConfig(): void
{
$firstWrite = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($firstWrite);
$secondWrite = $this->runWrite([
'module:label="demo-module"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($secondWrite);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
$this->assertSame('demo-module', $cfg['module']['label']);
}
public function testWriteWithSiteDirUsingConfigPrefixIsNormalized(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=config/owner_xyz',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
$this->assertFileDoesNotExist($this->tmpDir.'/config/config/owner_xyz/Extension.conf.php');
}
public function testWriteWithSiteDirUsingAbsoluteConfigPrefixIsNormalized(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir='.$this->tmpDir.'/config/owner_xyz',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
}
public function testWriteWithNestedSiteDirWritesToNestedSiteConfig(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz/sub_a',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/sub_a/Extension.conf.php');
}
public function testWriteWithSiteDirWritesToSiteConfig(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
}
public function testWriteToExistingSiteConfigCreatesBackup(): void
{
$firstWrite = $this->runWrite([
'module:code="first"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($firstWrite);
$secondWrite = $this->runWrite([
'module:code="second"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($secondWrite);
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
$backupFile = $siteFile.'.bak';
$this->assertFileExists($backupFile);
$current = require $siteFile;
$backup = require $backupFile;
$this->assertSame('second', $current['module']['code']);
$this->assertSame('first', $backup['module']['code']);
}
/* }}} */
/* Show Flow {{{ */
public function testShowWithSiteReturnsSiteSpecificValue(): void
{
$this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$show = $this->runShow([
'module:code',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($show);
$this->assertStringContainsString('X100', $show['output']);
}
/* }}} */
/* Validation {{{ */
public function testInvalidActionReturnsError(): void
{
$result = $this->runCfg([
'-a',
$this->tmpDir,
'delete',
'Extension',
]);
$this->assertCfgFailure($result, "'delete' not found");
}
public function testSiteDirRejectsEmptyValue(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=',
]);
$this->assertCfgFailure($result, 'a value is required for --siteDir');
}
/**
* @dataProvider siteDirValidationDataProvider
*/
public function testSiteDirValidation(string $siteDir, string $expectedMessage): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir='.$siteDir,
]);
$this->assertCfgFailure($result, $expectedMessage);
}
/**
* @dataProvider inputFileValidationDataProvider
*/
public function testInputFileValidation(?string $fileContents, string $pathSuffix, string $expectedMessage): void
{
$inFile = $this->tmpDir.'/'.$pathSuffix;
if ($fileContents !== null) {
file_put_contents($inFile, $fileContents);
}
$result = $this->runWrite([
'-i',
$inFile,
]);
$this->assertCfgFailure($result, $expectedMessage);
}
/**
* @dataProvider writePayloadSourceValidationDataProvider
*/
public function testWritePayloadSourceValidation(array $extraArgs, string $expectedMessage): void
{
$inFile = $this->createJsonInputFile(['module' => ['code' => 'X100']]);
$args = array_merge(
[
'-a',
$this->tmpDir,
'write',
'Extension',
],
array_map(
fn (string $arg): string => $arg === '__INPUT_FILE__' ? $inFile : $arg,
$extraArgs
)
);
$result = $this->runCfg($args);
$this->assertCfgFailure($result, $expectedMessage);
}
public function siteDirValidationDataProvider(): array
{
return [
'traversal' => ['../owner_xyz', 'Invalid directory in --siteDir'],
'invalid segment' => ['owner xyz', "Invalid directory name segment 'owner xyz' in --siteDir."],
'hidden dot segment' => ['owner_xyz/./secret', "Invalid directory in --siteDir: 'owner_xyz/./secret'."],
'config root only' => ['config/', 'Option --siteDir is empty.'],
];
}
public function inputFileValidationDataProvider(): array
{
return [
'missing file' => [null, 'missing.json', 'Input file is not readable'],
'invalid json' => ['{"module":', 'invalid.json', 'Input JSON is invalid'],
'scalar json' => ['true', 'scalar.json', 'Input JSON must decode to an object/array'],
];
}
public function writePayloadSourceValidationDataProvider(): array
{
return [
'missing payload source' => [[], 'Nothing to write: provide SETTING or --in.'],
'both payload sources' => [['module:code="x"', '-i', '__INPUT_FILE__'], 'Please use either SETTING or --in, not both.'],
];
}
/* }}} */
/* Helpers {{{ */
private function runWrite(array $args): array
{
return $this->runCfg(array_merge([
'-a',
$this->tmpDir,
'write',
'Extension',
], $args));
}
private function runShow(array $args): array
{
return $this->runCfg(array_merge([
'-a',
$this->tmpDir,
'show',
'Extension',
], $args));
}
private function createJsonInputFile(array $contents, string $name = 'extension-in.json'): string
{
$path = $this->tmpDir.'/'.$name;
file_put_contents($path, json_encode($contents, JSON_PRETTY_PRINT));
return $path;
}
private function loadWrittenConfig(?string $siteDir = null): array
{
$path = $siteDir === null
? $this->tmpDir.'/config/Extension.conf.php'
: $this->tmpDir.'/config/'.$siteDir.'/Extension.conf.php';
return require $path;
}
private function assertCfgSuccess(array $result): void
{
$this->assertSame(0, $result['code'], $result['output']);
}
private function assertCfgFailure(array $result, string $message): void
{
$this->assertSame(1, $result['code'], $result['output']);
$this->assertStringContainsString($message, $result['output']);
}
private function runCfg(array $args): array
{
$script = realpath(__DIR__.'/../bin/cfg');
$this->assertNotFalse($script);
$command = escapeshellarg(PHP_BINARY).' '.escapeshellarg($script);
foreach ($args as $arg) {
$command .= ' '.escapeshellarg($arg);
}
$command .= ' 2>&1';
$outputLines = [];
$exitCode = 0;
exec($command, $outputLines, $exitCode);
return [
'code' => $exitCode,
'output' => implode(PHP_EOL, $outputLines),
];
}
private function removeDir(string $dir): void
{
if (!is_dir($dir)) {
return;
}
foreach (scandir($dir) ?: [] as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$path = $dir.'/'.$entry;
if (is_dir($path)) {
$this->removeDir($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
/* }}} */
}

View file

@ -30,7 +30,7 @@ class SettingsTest extends TestCase
public function testConstruct() public function testConstruct()
{ {
$cfg = new Settings(); $cfg = new Settings();
$this->assertIsObject($cfg); $this->assertNotEmpty($cfg);
} }
/** /**
@ -69,7 +69,7 @@ class SettingsTest extends TestCase
{ {
$cfg = new Settings(); $cfg = new Settings();
$cfg = $this->appPath($cfg)->load(); $cfg = $this->appPath($cfg)->load();
$this->assertIsObject($cfg); $this->assertNotEmpty($cfg);
return $cfg; return $cfg;
} }
@ -129,23 +129,7 @@ class SettingsTest extends TestCase
$this->assertEquals('site', $cfg->testFile); $this->assertEquals('site', $cfg->testFile);
$this->assertEquals(42, $cfg->answer); $this->assertEquals(42, $cfg->answer);
} }
/**
*/
public function testLoadingOrder()
{
$cfg = new Settings();
$cfg = $this->appPath($cfg, 'order')->site('site')->load();
$this->assertEquals('default', $cfg->testFiles);
$this->assertEquals('conf', $cfg->testFiles2);
$this->assertEquals('site', $cfg->testFile);
$this->assertEquals(42, $cfg->answer);
return $cfg;
}
/** /**
*/ */
public function testTestingOverride() public function testTestingOverride()
@ -192,15 +176,6 @@ class SettingsTest extends TestCase
$cfg = $this->appPath($cfg, 'localOverride')->load(); $cfg = $this->appPath($cfg, 'localOverride')->load();
$this->assertEquals(42, $cfg->answer); $this->assertEquals(42, $cfg->answer);
} }
public function testLoadWithoutModeFallsBackToFirstKnownMode(): void
{
$cfg = new Settings();
$cfg = $this->appPath($cfg, 'noMode')->load();
$this->assertEquals('prod', $cfg->mode);
$this->assertEquals('default', $cfg->testFiles);
}
/** /**
* @dataProvider fileNameData * @dataProvider fileNameData
@ -220,22 +195,6 @@ class SettingsTest extends TestCase
$this->assertEquals($path.$expected, $cfg->buildFileName($type)); $this->assertEquals($path.$expected, $cfg->buildFileName($type));
} }
public function testBuildFileNameWithPrefixAndSite(): void
{
$cfg = new Settings();
$cfg->appPath('./')->prefix('Extension')->site('owner_xyz');
$this->assertEquals('./config/owner_xyz/Extension.conf.php', $cfg->buildFileName(0x01));
}
public function testBuildFileNameWithPrefixAndNestedSite(): void
{
$cfg = new Settings();
$cfg->appPath('./')->prefix('Extension')->site('owner_xyz/sub_a');
$this->assertEquals('./config/owner_xyz/sub_a/Extension.conf.php', $cfg->buildFileName(0x01));
}
public function fileNameData() public function fileNameData()
{ {
@ -265,4 +224,4 @@ class SettingsTest extends TestCase
/* jEdit buffer local properties {{{ /* jEdit buffer local properties {{{
* :folding=explicit:collapseFolds=1: * :folding=explicit:collapseFolds=1:
}}}*/ }}}*/

View file

@ -1,3 +0,0 @@
<?php return [
'testFiles' => 'default',
];

View file

@ -1,5 +0,0 @@
<?php return [
'answer' => 1,
'testFile' => 'conf',
'testFiles2' => 'conf'
];

View file

@ -1,7 +0,0 @@
<?php return [
'mode' => 'prod',
'answer' => 0,
'testFile' => 'default',
'testFiles' => 'default',
'testFiles2' => 'default'
];

View file

@ -1,4 +0,0 @@
<?php return [
'answer' => 42,
'testFile' => 'site'
];