#!/usr/bin/env php 'site', 'value' => $rawArgv[$idx + 1] ?? '', ]; if (isset($rawArgv[$idx + 1])) $idx++; continue; } if (str_starts_with($arg, '--site=')) { $preparsedSiteOptions[] = [ 'name' => 'site', 'value' => substr($arg, strlen('--site=')), ]; continue; } if ($arg === '--directory-name' || $arg === '--directoryName') { $preparsedSiteOptions[] = [ 'name' => 'directoryName', 'value' => $rawArgv[$idx + 1] ?? '', ]; if (isset($rawArgv[$idx + 1])) $idx++; continue; } if (str_starts_with($arg, '--directory-name=')) { $preparsedSiteOptions[] = [ 'name' => 'directoryName', 'value' => substr($arg, strlen('--directory-name=')), ]; continue; } if (str_starts_with($arg, '--directoryName=')) { $preparsedSiteOptions[] = [ 'name' => 'directoryName', 'value' => substr($arg, strlen('--directoryName=')), ]; continue; } $filteredArgv[] = $arg; } $_SERVER['argv'] = $filteredArgv; $GLOBALS['argv'] = $filteredArgv; } $version = '0.4'; $actions = [ 'show', 'write', 'help' ]; $settings = ['key' => '', 'value' => '']; /* Flags and Argument configuration {{{*/ $collection = (new Input\InputCollection()) ->add( Input\InputTypeFactory::build('LongOption')->name('help')->short('h') // {{{ ->flags(AbstractInputType::FLAG_OPTIONAL) ->description('Display help text') ) // }}} ->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('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) ->description('Set a mode') ) // }}} ->add( Input\InputTypeFactory::build('LongOption')->name('appPath')->short('a') // {{{ ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->description('Path where the config/ directory for the conf files is located, defaults to the working dir') ) // }}} ->add( Input\InputTypeFactory::build('LongOption')->name('pkgPath')->short('p') // {{{ ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->description('Path where the config/ directory of the package conf files is located, defaults to the working dir') ) // }}} ->add( Input\InputTypeFactory::build('LongOption')->name('site')->short('s') // {{{ ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->description('Site/instance directory below config/ (target: config//.conf.php), e.g. owner_xyz') ) // }}} ->add( Input\InputTypeFactory::build('LongOption')->name('directoryName') // {{{ ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->description('Alias for --site. Accepts owner_xyz or config/owner_xyz (also supports --directory-name)') ) // }}} ->add( Input\InputTypeFactory::build('Argument')->name('action') // {{{ ->flags(AbstractInputType::FLAG_REQUIRED) ->description( 'one of:' ."\n\t".'show reads configurations' ."\n\t".'write writes configurations' ) ->validator(new Input\Validator( function (AbstractInputType $input, AbstractInputHandler $context) use ($actions) { $action = $context->find('action'); if ( !in_array($action, $actions) ) { throw new \Exception("'$action' not found"); } return $action; } )) ) // }}} ->add( Input\InputTypeFactory::build('Argument')->name('prefix') // {{{ ->flags(AbstractInputType::FLAG_REQUIRED) ->description( 'the settings prefix' ) /* ->validator(new Input\Validator( function (AbstractInputType $input, AbstractInputHandler $context) use ($actions) { $action = $context->find('action'); if ( !in_array($action, $actions) { throw new \Exception("'$action' not found"); } return $action; } )) */ ) // }}} ->add( Input\InputTypeFactory::build('Argument')->name('setting') // {{{ ->flags(AbstractInputType::FLAG_OPTIONAL) ->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'); $setting = explode('=', $setting); $settings['key'] = $setting[0]; if ($action === 'write') { $value = $setting[1]; if (! (isset($value) || $context->find('data'))) { throw new \Exception('You need a value to write'); } $specialValues = [ 'true', 'false', 'null' ]; if (! (in_array($value, $specialValues) || is_numeric($value) || strpbrk($value, '[{":') !== false )) { $value = "\"$value\""; } try { $settings['value'] = json_decode($value, true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException $ex) { throw new \Exception( sprintf( 'The value does not appear to be a valid JSON string. Returned: %s: %s', $ex->getCode(), $ex->getMessage() )); } } return $settings; } )) ) // }}} ; // }}} /* Parse Input, usage, unkown flags, Help {{{*/ $usage = Cli\manpage( basename(__FILE__), $version, 'read write settings', $collection, Colour::FG_GREEN, Colour::FG_WHITE, [ 'Examples' => 'cfg write myCEESV \'auth:projectId="218523"\'' .PHP_EOL .'# writes local config: config/myCEESV.conf.php' .PHP_EOL .'cfg write myCEESV \'auth:projectId="218523"\' --directory-name=owner_xyz' .PHP_EOL .'# writes instance config: config/owner_xyz/myCEESV.conf.php' .PHP_EOL .'cfg show myCEESV auth:projectId --site=owner_xyz' .PHP_EOL .'# reads merged config including config/owner_xyz/myCEESV.conf.php' .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 { $argv = Input\InputHandlerFactory::build('Argv', $collection); } catch (\Exception $ex) { echo $usage; if (isset($argv[1])) { if ($argv[1] == '-h' || $argv[1] == '--help' || $argv[1] == 'help') { exit(0); } } fwrite(STDERR, $ex->getMessage().PHP_EOL); exit(1); } // show help if ($argv->find( 'help' ) || $argv->find('action') == 'help') { echo $usage; exit(0); } //}}} $prefix = $argv->find('prefix'); /* 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, '/').'/'); $site = null; $siteFlag = 0x01; // Only one site selector may be provided to avoid ambiguous targets. $providedSiteOptions = []; foreach ($preparsedSiteOptions as $option) { $providedSiteOptions[$option['name']][] = $option['value'] ?? ''; } foreach (['site', 'directoryName'] as $optionName) { $parsedValue = $argv->find($optionName); if ($parsedValue !== null && $parsedValue !== '' && $parsedValue !== false) { $providedSiteOptions[$optionName][] = $parsedValue; } } if (count($providedSiteOptions) > 1) { fwrite(STDERR, 'Please use only one of --site or --directoryName (alias: --directory-name).'.PHP_EOL); exit(1); } if (!empty($providedSiteOptions)) { $optionName = array_key_first($providedSiteOptions); $siteInput = trim((string)end($providedSiteOptions[$optionName])); if ($optionName === 'directoryName') { // 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 === '') { fwrite(STDERR, "Option --$optionName is empty.".PHP_EOL); exit(1); } // Block directory traversal and hidden-dot segments. if (str_contains($siteInput, '..') || preg_match('~(^|/)\.(?:/|$)~', $siteInput)) { fwrite(STDERR, "Invalid directory in --$optionName: '$siteInput'.".PHP_EOL); exit(1); } // Allow only predictable path segments for instance directories. foreach (explode('/', $siteInput) as $part) { if ($part === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $part)) { fwrite(STDERR, "Invalid directory name segment '$part' in --$optionName.".PHP_EOL); exit(1); } } $site = $siteInput; // Reuse util-settings site resolution: config//.conf.php $cfg->site($site); } try { if (is_readable($cfg->buildFileName('default'))) { $cfg->load(); } elseif (is_readable($cfgFile = $cfg->buildFileName())) { $cfg->load(require($cfgFile)); } } catch (\Throwable $e) { fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL); exit(1); } //var_dump($cfg); $result = $cfg; $settings = $argv->find('setting') ?? $settings; if ($settings['key']) { foreach (explode(':', $settings['key']) as $setting) { try { $result = $result->{$setting}; } catch (\OutOfRangeException $e) { fwrite(STDERR, $e->getMessage().PHP_EOL); exit(1); } } } else $result = $cfg; if ($result instanceof Settings) $result = $result->toArray(); switch ($argv->find('action')) { case 'show': $out = (is_string($result)) ? $result : json_encode($result, JSON_PRETTY_PRINT); echo $out.PHP_EOL; break; case 'write': $path = ($settings['key'] !== '') ? explode(':', $settings['key']) : []; $setting2write = $settings['value']; while ( ! empty($path)) { $setting2write = [array_pop($path) => $setting2write]; } $writeType = ($site !== null) ? $siteFlag : null; $file = $cfg->buildFileName($writeType); if (is_readable($file)) { $setting2write = array_replace_recursive(require($file), $setting2write); copy($file, "$file.bak"); } $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); // var_dump($writeCfg->toArray()); try { (new SettingsWriter($writeCfg, '', $writeType))->write(); echo "Written modified settings to: $file".PHP_EOL; } catch (\Exception $e) { fwrite(STDERR, $e->getMessage().PHP_EOL); exit(1); } break; } /* jEdit buffer local properties {{{ * :folding=explicit:collapseFolds=1: }}}*/