diff --git a/bin/cfg b/bin/cfg index 3c7c0ef..78e84c5 100755 --- a/bin/cfg +++ b/bin/cfg @@ -22,12 +22,7 @@ foreach ($autoloadFiles as $autoloadFile) { } } -function verboseLog(bool $enabled, string $message): void -{ - if ($enabled) fwrite(STDERR, "[verbose] $message".PHP_EOL); -} - -$version = '0.4'; +$version = '0.3'; $actions = [ 'show', 'write', 'help' ]; $settings = ['key' => '', 'value' => '']; @@ -40,55 +35,15 @@ $collection = (new Input\InputCollection()) ->description('Display help text') ) // }}} - ->add( Input\InputTypeFactory::build('LongOption')->name('verbose')->short('v') // {{{ - ->flags(AbstractInputType::FLAG_OPTIONAL) - ->description('Print debug details to STDERR') + ->add( Input\InputTypeFactory::build('LongOption')->name('in')->short('i') // {{{ + ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) + ->description('Path to a json data file to to read') ) // }}} - ->add( Input\InputTypeFactory::build('LongOption')->name('in')->short('i') // {{{ - ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) - ->description('Path to a JSON data file to read (for write)') - ->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('out')->short('o') // {{{ + ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) + ->description('Path to a json file to to write to') + ) // }}} ->add( Input\InputTypeFactory::build('LongOption')->name('mode')->short('m') // {{{ ->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') ) // }}} - ->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') // {{{ ->flags(AbstractInputType::FLAG_REQUIRED) ->description( @@ -201,30 +102,18 @@ $collection = (new Input\InputCollection()) ->description( '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( - function (AbstractInputType $input, AbstractInputHandler $context) - { - $setting = $context->find('setting'); - $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]; - } - + ->validator(new Input\Validator( + function (AbstractInputType $input, AbstractInputHandler $context) + { + $setting = $context->find('setting'); + $action = $context->find('action'); + $setting = explode('=', $setting); $settings['key'] = $setting[0]; if ($action === 'write') { - $value = $setting[1] ?? null; - if (! (isset($value) || $context->find('in'))) { + $value = $setting[1]; + if (! (isset($value) || $context->find('data'))) { throw new \Exception('You need a value to write'); } $specialValues = [ 'true', 'false', 'null' ]; @@ -250,50 +139,18 @@ $collection = (new Input\InputCollection()) $usage = Cli\manpage( basename(__FILE__), $version, 'read write settings', $collection, Colour::FG_GREEN, Colour::FG_WHITE, - [ - 'Examples' => - 'Basic usage:' - .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 - ] + [ + 'Examples' => + 'cfg show VeruA db:host'.PHP_EOL. + 'cfg write VeruA \'db:host="newHost"\''.PHP_EOL + ] ).PHP_EOL; // Get the supplied input. Passing the collection will make the handler bind values // and validate the input according to our collection -try -{ +try { $argv = Input\InputHandlerFactory::build('Argv', $collection); -} -catch (\Exception $ex) -{ +} catch (\Exception $ex) { echo $usage; if (isset($argv[1])) { @@ -315,41 +172,42 @@ if ($argv->find( 'help' ) || $argv->find('action') == 'help') $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'); 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'); $cfg = (new Settings([], $mode))->appPath($appPath)->prefix($prefix); -// pkgPath points to package defaults (e.g. .default.conf.php) -if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath(rtrim($pkgPath, '/').'/'); +if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath($pkgPath); -$site = null; -$siteFlag = 0x01; -$site = $argv->find('siteDir'); -if ($site !== null && $site !== false) -{ - // Reuse util-settings site resolution: config//.conf.php - $cfg->site($site); - verboseLog($verbose, "siteDir resolved to '$site'"); -} - -try -{ +try { if (is_readable($cfg->buildFileName('default'))) { $cfg->load(); } elseif (is_readable($cfgFile = $cfg->buildFileName())) { $cfg->load(require($cfgFile)); } -} -catch (\Throwable $e) -{ +} catch (Exception $e) { fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL); exit(1); } -verboseLog($verbose, 'config bootstrap loaded'); +//var_dump($cfg); $result = $cfg; $settings = $argv->find('setting') ?? $settings; @@ -374,63 +232,33 @@ if ($result instanceof Settings) $result = $result->toArray(); switch ($argv->find('action')) { case 'show': - verboseLog($verbose, "show action for prefix '$prefix'"); $out = (is_string($result)) ? $result : json_encode($result, JSON_PRETTY_PRINT); echo $out.PHP_EOL; break; 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']) : []; - 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)) { $setting2write = [array_pop($path) => $setting2write]; } - - $writeType = ($site !== null && $site !== false) ? $siteFlag : null; - $file = $cfg->buildFileName($writeType); - verboseLog($verbose, "write target: $file"); - if (is_readable($file)) + if (is_readable($file = $cfg->buildFileName())) { $setting2write = array_replace_recursive(require($file), $setting2write); 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); - verboseLog($verbose, 'payload prepared for writer'); - try - { - (new SettingsWriter($writeCfg, '', $writeType))->write(); +// var_dump($writeCfg->toArray()); + try { + (new SettingsWriter($writeCfg))->write(); echo "Written modified settings to: $file".PHP_EOL; } catch (\Exception $e) { - fwrite(STDERR, $e->getMessage().PHP_EOL); - exit(1); + echo $e->getMessage().PHP_EOL; } break; diff --git a/composer.json b/composer.json index 5905b59..336c8bf 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { "name": "rabe/util-settings", "description": "Package for reading and writing configuration settings", - "bin": ["bin/cfg"], "license": "AGPL3", "authors": [ { diff --git a/composer.lock b/composer.lock index 5974213..671abee 100644 --- a/composer.lock +++ b/composer.lock @@ -498,30 +498,30 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -548,7 +548,7 @@ ], "support": { "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": [ { @@ -564,20 +564,20 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -585,12 +585,11 @@ }, "conflict": { "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": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -616,7 +615,7 @@ ], "support": { "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": [ { @@ -624,31 +623,29 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.4" + "php": ">=7.0" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -656,7 +653,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "4.9-dev" } }, "autoload": { @@ -680,27 +677,26 @@ ], "support": { "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", - "version": "2.0.4", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { "ext-dom": "*", - "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -741,15 +737,9 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "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": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", @@ -804,35 +794,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.32", + "version": "9.2.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", + "nikic/php-parser": "^4.15", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-text-template": "^2.0.4", - "sebastian/code-unit-reverse-lookup": "^2.0.3", - "sebastian/complexity": "^2.0.3", - "sebastian/environment": "^5.1.5", - "sebastian/lines-of-code": "^1.0.4", - "sebastian/version": "^3.0.2", - "theseer/tokenizer": "^1.2.3" + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -841,7 +831,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.2.x-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -869,8 +859,7 @@ ], "support": { "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.32" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" }, "funding": [ { @@ -878,7 +867,7 @@ "type": "github" } ], - "time": "2024-08-22T04:23:01+00:00" + "time": "2023-03-06T12:58:08+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1123,45 +1112,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "9.6.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.5.0 || ^2", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.32", - "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.4", - "phpunit/php-timer": "^5.0.3", - "sebastian/cli-parser": "^1.0.2", - "sebastian/code-unit": "^1.0.8", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.6", - "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", - "sebastian/object-enumerator": "^4.0.4", - "sebastian/resource-operations": "^3.0.4", - "sebastian/type": "^3.2.1", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1205,8 +1194,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" }, "funding": [ { @@ -1222,20 +1210,20 @@ "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2023-03-09T06:34:10+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.2", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", "shasum": "" }, "require": { @@ -1270,7 +1258,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "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": [ { @@ -1278,7 +1266,7 @@ "type": "github" } ], - "time": "2024-03-02T06:27:43+00:00" + "time": "2020-09-28T06:08:49+00:00" }, { "name": "sebastian/code-unit", @@ -1467,20 +1455,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.3", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.7", "php": ">=7.3" }, "require-dev": { @@ -1512,7 +1500,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "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": [ { @@ -1520,20 +1508,20 @@ "type": "github" } ], - "time": "2023-12-22T06:19:30+00:00" + "time": "2020-10-26T15:52:27+00:00" }, { "name": "sebastian/diff", - "version": "4.0.6", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", "shasum": "" }, "require": { @@ -1578,7 +1566,7 @@ ], "support": { "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": [ { @@ -1586,7 +1574,7 @@ "type": "github" } ], - "time": "2024-03-02T06:30:58+00:00" + "time": "2020-10-26T13:10:38+00:00" }, { "name": "sebastian/environment", @@ -1653,16 +1641,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { @@ -1718,7 +1706,7 @@ ], "support": { "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": [ { @@ -1726,20 +1714,20 @@ "type": "github" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -1782,7 +1770,7 @@ ], "support": { "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": [ { @@ -1790,24 +1778,24 @@ "type": "github" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.4", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.6", "php": ">=7.3" }, "require-dev": { @@ -1839,7 +1827,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "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": [ { @@ -1847,7 +1835,7 @@ "type": "github" } ], - "time": "2023-12-22T06:20:34+00:00" + "time": "2020-11-28T06:42:11+00:00" }, { "name": "sebastian/object-enumerator", @@ -2026,16 +2014,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.4", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", "shasum": "" }, "require": { @@ -2047,7 +2035,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2068,7 +2056,8 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "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": [ { @@ -2076,7 +2065,7 @@ "type": "github" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2020-09-28T06:45:17+00:00" }, { "name": "sebastian/type", @@ -2189,16 +2178,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -2227,7 +2216,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "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": [ { @@ -2235,7 +2224,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [ diff --git a/src/Settings.php b/src/Settings.php index ebf7aeb..fee1ede 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -29,7 +29,7 @@ namespace rabe\Util; class Settings implements \Iterator, \Countable { private const SITE = 0x01; - + private array $settings = []; private string $appPath = './'; @@ -68,7 +68,7 @@ class Settings implements \Iterator, \Countable public function appPath( ?string $path=null ) { if (! isset($path)) return $this->appPath; - + if ($this->pkgPath === $this->appPath) $this->pkgPath = $path; $this->appPath = $path; return $this; @@ -105,7 +105,7 @@ class Settings implements \Iterator, \Countable $this->modes += $modes; return $this; }// }}} - + // site() {{{ /** {{{ *///}}} @@ -114,7 +114,7 @@ class Settings implements \Iterator, \Countable $this->site = $site; return $this; }// }}} - + // load() method {{{ /** {{{ * Load the configuration from the files and merge them @@ -129,12 +129,12 @@ class Settings implements \Iterator, \Countable } else { - + $modes = $this->modes; - + // load Prefix.default.conf.php $defaultConf = $this->buildFileName( array_shift($modes) ); - + if ( file_exists($defaultConf) ) { $this->settings = require( $defaultConf ); @@ -145,45 +145,34 @@ class Settings implements \Iterator, \Countable throw new \Exception( "No settingsfile: $defaultConf" ); // throw new FileNotFoundException(); } - + // add modes from the drfault config file (no modes from other files are added) if (isset($this->settings['modes'])) { $this->addModes($this->settings['modes']); } - + // load local config without merging - we need it here for the mode $conf = $this->buildFileName(); - $localConf = []; + $localConf = false; if (file_exists($conf)) $localConf = require($conf); - + // if a mode was set in the constructor do not overwrite it if (! isset($this->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") - 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; - } + $this->mode = (isset($localConf['mode'])) ? $localConf['mode'] : $this->settings['mode']; } else { $this->settings['mode'] = $this->mode; } - + if ( isset($this->site) ) { $siteConf = $this->buildFileName(self::SITE); 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 if ( isset( $this->modes[ $this->mode ] ) ) { @@ -203,14 +192,14 @@ class Settings implements \Iterator, \Countable // log invalid mode throw new \Exception( "The mode '{$this->mode}' is not valid" ); } - + // merge Prefix.conf.php (localConf) if ($localConf !== false) { $this->settings = array_replace_recursive( $this->settings, $localConf ); } } - + // echo "
"; print_r( $this->settings ); echo "
"; return $this; }// }}} @@ -220,9 +209,9 @@ class Settings implements \Iterator, \Countable *///}}} public function mergeFile( ) { - + }// }}} - + // create() {{{ /** {{{ * Creates a copy of the Settingsobject without the configuration directives @@ -244,15 +233,15 @@ class Settings implements \Iterator, \Countable { $mode = ''; $path = $this->appPath.$this->confDir; - + if ($type === self::SITE) $path .= $this->site.'/'; - + if (is_string( $type ) &! empty( $type )) { $mode = "$type."; $path = $this->pkgPath.$this->confDir; } - + return $path.$this->filePrefix.$mode.$this->filePostfix; } // }}} @@ -281,7 +270,7 @@ class Settings implements \Iterator, \Countable * Rewind the Iterator to the first element * @link https://www.php.net/iterator.rewind */ - public function rewind(): void + public function rewind() { reset( $this->settings); } @@ -290,7 +279,7 @@ class Settings implements \Iterator, \Countable * Return the current element * @link https://www.php.net/iterator.current */ - public function current(): mixed + public function current() { return current( $this->settings ); } @@ -299,7 +288,7 @@ class Settings implements \Iterator, \Countable * Return the key of the current element * @link https://www.php.net/iterator.key */ - public function key(): mixed + public function key() { return key( $this->settings ); } @@ -308,16 +297,16 @@ class Settings implements \Iterator, \Countable * Move forward to next element * @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 * @link https://www.php.net/iterator.valid */ - public function valid(): bool + public function valid() { $key = key( $this->settings ); return ($key !== null && $key !== false); @@ -330,12 +319,12 @@ class Settings implements \Iterator, \Countable * Count elements * @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 {{{ public function __set( $name, $value ) { @@ -363,8 +352,17 @@ class Settings implements \Iterator, \Countable { 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 {{{ diff --git a/src/SettingsWriter.php b/src/SettingsWriter.php index 6246a9f..d6fc41c 100644 --- a/src/SettingsWriter.php +++ b/src/SettingsWriter.php @@ -22,8 +22,6 @@ declare(strict_types=1); namespace rabe\Util; -use ErrorException; - /** * Write Settings into a File * @author Norbert.e.Wagner dev@norb.me @@ -47,16 +45,13 @@ class SettingsWriter * * @param Settings $settings an object of type settings * @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='', $type=null ) + */ + public function __construct( Settings $settings, $name='' ) { $this->settings = $settings; - - $file = ( $type === null ) - ? $settings->buildFileName( $name ) - : $settings->buildFileName( $type ); - + + $file = $settings->buildFileName( $name ); + if ( ! $this->handle = fopen( $file, 'w' ) ) { throw new ErrorException( "Can not open File: $file" ); diff --git a/tests/CfgTest.php b/tests/CfgTest.php deleted file mode 100644 index 646bcb2..0000000 --- a/tests/CfgTest.php +++ /dev/null @@ -1,457 +0,0 @@ -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', - " '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', - " [\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); - } - /* }}} */ -} diff --git a/tests/SettingsTest.php b/tests/SettingsTest.php index 09f311c..dd78e34 100644 --- a/tests/SettingsTest.php +++ b/tests/SettingsTest.php @@ -30,7 +30,7 @@ class SettingsTest extends TestCase public function testConstruct() { $cfg = new Settings(); - $this->assertIsObject($cfg); + $this->assertNotEmpty($cfg); } /** @@ -69,7 +69,7 @@ class SettingsTest extends TestCase { $cfg = new Settings(); $cfg = $this->appPath($cfg)->load(); - $this->assertIsObject($cfg); + $this->assertNotEmpty($cfg); return $cfg; } @@ -129,23 +129,7 @@ class SettingsTest extends TestCase $this->assertEquals('site', $cfg->testFile); $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() @@ -192,15 +176,6 @@ class SettingsTest extends TestCase $cfg = $this->appPath($cfg, 'localOverride')->load(); $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 @@ -220,22 +195,6 @@ class SettingsTest extends TestCase $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() { @@ -265,4 +224,4 @@ class SettingsTest extends TestCase /* jEdit buffer local properties {{{ * :folding=explicit:collapseFolds=1: -}}}*/ +}}}*/ \ No newline at end of file diff --git a/tests/cfg/noMode/config/default.conf.php b/tests/cfg/noMode/config/default.conf.php deleted file mode 100644 index c0a0d9b..0000000 --- a/tests/cfg/noMode/config/default.conf.php +++ /dev/null @@ -1,3 +0,0 @@ - 'default', -]; diff --git a/tests/cfg/order/config/conf.php b/tests/cfg/order/config/conf.php deleted file mode 100644 index 8ea09df..0000000 --- a/tests/cfg/order/config/conf.php +++ /dev/null @@ -1,5 +0,0 @@ - 1, - 'testFile' => 'conf', - 'testFiles2' => 'conf' -]; diff --git a/tests/cfg/order/config/default.conf.php b/tests/cfg/order/config/default.conf.php deleted file mode 100644 index 90020ca..0000000 --- a/tests/cfg/order/config/default.conf.php +++ /dev/null @@ -1,7 +0,0 @@ - 'prod', - 'answer' => 0, - 'testFile' => 'default', - 'testFiles' => 'default', - 'testFiles2' => 'default' -]; diff --git a/tests/cfg/order/config/site/conf.php b/tests/cfg/order/config/site/conf.php deleted file mode 100644 index 54ad2b8..0000000 --- a/tests/cfg/order/config/site/conf.php +++ /dev/null @@ -1,4 +0,0 @@ - 42, - 'testFile' => 'site' -];