Task: #53687 Support instance-specific cfg write targets and add coverage V2 #4
1 changed files with 62 additions and 28 deletions
Task: #53687 Align cfg brace style to project conventions
commit
29d1da4ac5
90
bin/cfg
90
bin/cfg
|
|
@ -24,18 +24,23 @@ foreach ($autoloadFiles as $autoloadFile) {
|
||||||||
|
|
||||||||
function readJsonInputFile(string $path): array
|
function readJsonInputFile(string $path): array
|
||||||||
|
alejandro.sosa marked this conversation as resolved
Outdated
alejandro.sosa marked this conversation as resolved
Outdated
norb
commented
can you do this as validators? e.g.
can you do this as validators? e.g. https://code.verua.online/rabe/helpers-cli-input/src/commit/c6fe64321ef06633c89bc055252f22d6ff676d52/example/example.php#L35
|
|||||||||
{
|
{
|
||||||||
if (!is_readable($path)) {
|
if (!is_readable($path))
|
||||||||
|
{
|
||||||||
throw new \RuntimeException("Input file is not readable: $path");
|
throw new \RuntimeException("Input file is not readable: $path");
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
$content = file_get_contents($path);
|
$content = file_get_contents($path);
|
||||||||
if ($content === false) {
|
if ($content === false)
|
||||||||
|
{
|
||||||||
throw new \RuntimeException("Can not read input file: $path");
|
throw new \RuntimeException("Can not read input file: $path");
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
try {
|
try
|
||||||||
|
{
|
||||||||
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
|
||||||||
} catch (\JsonException $ex) {
|
}
|
||||||||
|
catch (\JsonException $ex)
|
||||||||
|
{
|
||||||||
throw new \RuntimeException(sprintf(
|
throw new \RuntimeException(sprintf(
|
||||||||
'Input JSON is invalid (%s): %s',
|
'Input JSON is invalid (%s): %s',
|
||||||||
(string)$ex->getCode(),
|
(string)$ex->getCode(),
|
||||||||
|
|
@ -43,7 +48,8 @@ function readJsonInputFile(string $path): array
|
||||||||
));
|
));
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
if (!is_array($data)) {
|
if (!is_array($data))
|
||||||||
|
{
|
||||||||
throw new \RuntimeException('Input JSON must decode to an object/array');
|
throw new \RuntimeException('Input JSON must decode to an object/array');
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
|
|
@ -54,17 +60,21 @@ function readJsonInputFile(string $path): array
|
||||||||
// The input helper library does not handle this case reliably.
|
// The input helper library does not handle this case reliably.
|
||||||||
$preparsedSiteDir = null;
|
$preparsedSiteDir = null;
|
||||||||
$rawArgv = $_SERVER['argv'] ?? $GLOBALS['argv'] ?? null;
|
$rawArgv = $_SERVER['argv'] ?? $GLOBALS['argv'] ?? null;
|
||||||||
if (is_array($rawArgv) && !empty($rawArgv)) {
|
if (is_array($rawArgv) && !empty($rawArgv))
|
||||||||
|
{
|
||||||||
$filteredArgv = [$rawArgv[0]];
|
$filteredArgv = [$rawArgv[0]];
|
||||||||
for ($idx = 1; $idx < count($rawArgv); $idx++) {
|
for ($idx = 1; $idx < count($rawArgv); $idx++)
|
||||||||
|
{
|
||||||||
$arg = $rawArgv[$idx];
|
$arg = $rawArgv[$idx];
|
||||||||
|
|
||||||||
if ($arg === '--siteDir') {
|
if ($arg === '--siteDir')
|
||||||||
|
{
|
||||||||
$preparsedSiteDir = $rawArgv[$idx + 1] ?? '';
|
$preparsedSiteDir = $rawArgv[$idx + 1] ?? '';
|
||||||||
if (isset($rawArgv[$idx + 1])) $idx++;
|
if (isset($rawArgv[$idx + 1])) $idx++;
|
||||||||
continue;
|
continue;
|
||||||||
}
|
}
|
||||||||
if (str_starts_with($arg, '--siteDir=')) {
|
if (str_starts_with($arg, '--siteDir='))
|
||||||||
|
{
|
||||||||
$preparsedSiteDir = substr($arg, strlen('--siteDir='));
|
$preparsedSiteDir = substr($arg, strlen('--siteDir='));
|
||||||||
continue;
|
continue;
|
||||||||
}
|
}
|
||||||||
|
|
@ -224,9 +234,12 @@ $usage = Cli\manpage( basename(__FILE__), $version,
|
||||||||
|
|
||||||||
// 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])) {
|
||||||||
|
|
@ -277,29 +290,37 @@ if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath(rtrim($pkgPath, '/').'/');
|
||||||||
$site = null;
|
$site = null;
|
||||||||
$siteFlag = 0x01;
|
$siteFlag = 0x01;
|
||||||||
$siteInput = ($preparsedSiteDir !== null) ? $preparsedSiteDir : $argv->find('siteDir');
|
$siteInput = ($preparsedSiteDir !== null) ? $preparsedSiteDir : $argv->find('siteDir');
|
||||||||
if ($siteInput !== null && $siteInput !== false) {
|
if ($siteInput !== null && $siteInput !== false)
|
||||||||
|
alejandro.sosa marked this conversation as resolved
Outdated
norb
commented
probably most of this should be done in the validator as well probably most of this should be done in the validator as well
|
|||||||||
|
{
|
||||||||
$siteInput = trim((string)$siteInput);
|
$siteInput = trim((string)$siteInput);
|
||||||||
|
|
||||||||
// Accept both "owner_xyz" and "config/owner_xyz" and normalize to site key.
|
// Accept both "owner_xyz" and "config/owner_xyz" and normalize to site key.
|
||||||||
if (str_starts_with($siteInput, $appPath.'config/')) {
|
if (str_starts_with($siteInput, $appPath.'config/'))
|
||||||||
|
{
|
||||||||
$siteInput = substr($siteInput, strlen($appPath.'config/'));
|
$siteInput = substr($siteInput, strlen($appPath.'config/'));
|
||||||||
} elseif (str_starts_with($siteInput, 'config/')) {
|
}
|
||||||||
|
elseif (str_starts_with($siteInput, 'config/'))
|
||||||||
|
{
|
||||||||
$siteInput = substr($siteInput, strlen('config/'));
|
$siteInput = substr($siteInput, strlen('config/'));
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
$siteInput = trim($siteInput, '/');
|
$siteInput = trim($siteInput, '/');
|
||||||||
if ($siteInput === '') {
|
if ($siteInput === '')
|
||||||||
|
{
|
||||||||
fwrite(STDERR, 'Option --siteDir is empty.'.PHP_EOL);
|
fwrite(STDERR, 'Option --siteDir is empty.'.PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
// Block directory traversal and hidden-dot segments.
|
// Block directory traversal and hidden-dot segments.
|
||||||||
if (str_contains($siteInput, '..') || preg_match('~(^|/)\.(?:/|$)~', $siteInput)) {
|
if (str_contains($siteInput, '..') || preg_match('~(^|/)\.(?:/|$)~', $siteInput))
|
||||||||
|
{
|
||||||||
fwrite(STDERR, "Invalid directory in --siteDir: '$siteInput'.".PHP_EOL);
|
fwrite(STDERR, "Invalid directory in --siteDir: '$siteInput'.".PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
// Allow only predictable path segments for instance directories.
|
// Allow only predictable path segments for instance directories.
|
||||||||
foreach (explode('/', $siteInput) as $part) {
|
foreach (explode('/', $siteInput) as $part)
|
||||||||
if ($part === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $part)) {
|
{
|
||||||||
|
if ($part === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $part))
|
||||||||
|
{
|
||||||||
fwrite(STDERR, "Invalid directory name segment '$part' in --siteDir.".PHP_EOL);
|
fwrite(STDERR, "Invalid directory name segment '$part' in --siteDir.".PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
|
|
@ -310,14 +331,17 @@ if ($siteInput !== null && $siteInput !== false) {
|
||||||||
$cfg->site($site);
|
$cfg->site($site);
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
try {
|
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 (\Throwable $e) {
|
}
|
||||||||
|
catch (\Throwable $e)
|
||||||||
|
{
|
||||||||
fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL);
|
fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
|
|
@ -355,24 +379,32 @@ case 'write':
|
||||||||
// Write needs exactly one payload source:
|
// Write needs exactly one payload source:
|
||||||||
// either SETTING (key=value) or --in (JSON file).
|
// either SETTING (key=value) or --in (JSON file).
|
||||||||
// This avoids ambiguous input precedence and empty writes.
|
// This avoids ambiguous input precedence and empty writes.
|
||||||||
if ($inputFile && $settings['key'] !== '') {
|
if ($inputFile && $settings['key'] !== '')
|
||||||||
|
alejandro.sosa marked this conversation as resolved
Outdated
norb
commented
probably this one too? probably this one too?
|
|||||||||
|
{
|
||||||||
fwrite(STDERR, 'Please use either SETTING or --in, not both.'.PHP_EOL);
|
fwrite(STDERR, 'Please use either SETTING or --in, not both.'.PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
if (!$inputFile && $settings['key'] === '') {
|
if (!$inputFile && $settings['key'] === '')
|
||||||||
|
{
|
||||||||
fwrite(STDERR, 'Nothing to write: provide SETTING or --in.'.PHP_EOL);
|
fwrite(STDERR, 'Nothing to write: provide SETTING or --in.'.PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
$path = ($settings['key'] !== '') ? explode(':', $settings['key']) : [];
|
$path = ($settings['key'] !== '') ? explode(':', $settings['key']) : [];
|
||||||||
if ($inputFile) {
|
if ($inputFile)
|
||||||||
try {
|
{
|
||||||||
|
try
|
||||||||
|
{
|
||||||||
$setting2write = readJsonInputFile($inputFile);
|
$setting2write = readJsonInputFile($inputFile);
|
||||||||
} catch (\Throwable $e) {
|
}
|
||||||||
|
catch (\Throwable $e)
|
||||||||
|
{
|
||||||||
fwrite(STDERR, $e->getMessage().PHP_EOL);
|
fwrite(STDERR, $e->getMessage().PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
} else {
|
}
|
||||||||
|
else
|
||||||||
|
{
|
||||||||
$setting2write = $settings['value'];
|
$setting2write = $settings['value'];
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
|
|
@ -390,14 +422,16 @@ case 'write':
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
$targetDir = dirname($file);
|
$targetDir = dirname($file);
|
||||||||
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true) && !is_dir($targetDir)) {
|
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true) && !is_dir($targetDir))
|
||||||||
|
{
|
||||||||
fwrite(STDERR, "Can not create directory: $targetDir".PHP_EOL);
|
fwrite(STDERR, "Can not create directory: $targetDir".PHP_EOL);
|
||||||||
exit(1);
|
exit(1);
|
||||||||
}
|
}
|
||||||||
|
|
||||||||
$writeCfg = $cfg->create($setting2write);
|
$writeCfg = $cfg->create($setting2write);
|
||||||||
// var_dump($writeCfg->toArray());
|
// var_dump($writeCfg->toArray());
|
||||||||
try {
|
try
|
||||||||
|
{
|
||||||||
(new SettingsWriter($writeCfg, '', $writeType))->write();
|
(new SettingsWriter($writeCfg, '', $writeType))->write();
|
||||||||
echo "Written modified settings to: $file".PHP_EOL;
|
echo "Written modified settings to: $file".PHP_EOL;
|
||||||||
}
|
}
|
||||||||
|
|
|
||||||||
Loading…
Add table
Add a link
Reference in a new issue
I don't really like this .. seems over complicating the code. It is just for beging able to have different order of arguments?
not that important to me .. and probably better put inside the input helper library or replace it altogether .. IIRC the one used is not maintained anymore
@norb wrote in #4 (comment):
You're right. After reviewing it again with the alias cleanup in place, I realized we can keep this much simpler. The pre-parse block was added as a defensive workaround while multiple option variants were still in play, but with --siteDir now unified and a couple of small follow-up fixes, the standard parser flow is sufficient. I removed that extra layer to keep the code cleaner and easier to maintain.