How can I merge two strings that contain conventional CLI options (i.e. anything getopt()
would parse correctly.)?
The first is pulled from a config file, and the second is from Symfony's Console\Input\getArgument()
. I could also get the second one from $GLOBALS['argv']
. I want to combine them, so that I can launch another program with them.
Either string could contain short options, long options, both with or without values.
e.g., config.yml
contains
phpunit_arguments: -d xdebug.mode=off --columns=115
...and then the user can call my program with php {filename} --columns=95 --debug
. I want to merge those strings, so that I end up with:
-d xdebug.mode=off --columns=95 --debug
The columns
from the first string was overridden by the one from the second.
If I can get those strings into arrays like the following, then I can just use array_merge_recursive()
:
array(
'-d xdebug.mode' => 'off',
'--columns' => 115
)
array(
'--columns' => 95,
'--debug'
)
...but to do that I need a parser that understand CLI options.
I've looked at the following, but none seem to be designed to take an arbitrary string and return a parsed array.
getopt()
Console\Input
TextUI\CliArguments\Builder
I tried just concatenating the two strings instead of merging them, and that technically works, but it creates UX problems. My program displays the args to the users, and concat'd string would contain duplicates, which would be confusing for some. The program also accepts input while it's running, and regenerates the options; over time, appending to the prior string would snowball and worse the confusion/UX.
e.g., after setting groups
a few times, it'd end up as
-d xdebug.mode=off --columns=95 --debug --groups=database --groups=file --groups=console
You can create your own function that will smart merge configuration parameters and CLI arguments. Using a single regular expression, we can extract pre-signs like -
or --
, command names and equality character with the value.
Please read inline comments
<?php
echo "PHP ".phpversion().PHP_EOL;
// $config = yaml_parse_file('config.yml');
// Load a JSON file instead of YAML, because repl.it
// does not have php_yaml extension enabled
$json = file_get_contents("config.json");
$config = json_decode($json, TRUE);
$phpunit_arguments = explode(' ', $config['phpunit_arguments']);
echo "phpunit_arguments:\n";
print_r($phpunit_arguments);
// Merge the params
$params = mergeConfigParameters($phpunit_arguments);
// Concatenate and print all params
echo "cli args:\n";
echo implode(' ', $params).PHP_EOL;
function mergeConfigParameters($config_params) {
$argv = $GLOBALS['argv'];
array_shift($argv); // Remove script file from CLI arguments
if (empty($argv)) {
// If we run the file without CLI arguments,
// apply some fake argv arguments for demontration
$argv = [
'-d',
'xdebug.mode=on',
'--columns=95',
'--debug',
'--groups=database',
'--groups=file',
'--groups=console'
];
}
echo "argv:\n";
print_r($argv);
// Merge all parameters, CLI arguments and from config
$all_params = array_merge($config_params, $argv);
echo "all_params:\n";
print_r($all_params);
// We'll save all the params here using assoc array
// to identify and handle/override duplicate commands
$params = [];
foreach ($all_params as $param) {
// This regex will match everything:
// -d
// xdebug.mode=off
// --columns=95
// and create 4 groups:
// 1: the pre char(s), - or --
// 2: the cmd, actual command
// 3: the eq char, =
// 4: the value
if (preg_match('/^(-[-]?)?([\w.]+)(=?)(.*)/', $param, $matches)) {
// Destructure matches
[, $pre, $cmd, $eq, $value] = $matches;
$param = [
'pre' => $pre,
'cmd' => $cmd,
'eq' => $eq,
'value' => $value
];
// If the command is set, merge it with the previous,
// else add it to $params array
if (isset($params[$cmd])) {
$params[$cmd] = array_merge($params[$cmd], $param);
} else {
$params[$cmd] = $param;
}
}
}
$merged = [];
// Loop throu all unique params and re-build the commands
foreach ($params as $param) {
[
'pre' => $pre,
'cmd' => $cmd,
'eq' => $eq,
'value' => $value
] = $param;
if (!empty($pre)) {
$cmd = $pre.$cmd;
}
if (!empty($eq)) {
$cmd .= $eq;
if (!empty($value)) {
$cmd .= $value;
}
}
$merged[] = $cmd;
}
echo "merged:\n";
print_r($merged);
// Finally we have all unique commands compiled again
return $merged;
}
Running this command:
php main.php -d xdebug.mode=on --columns=95 --debug --groups=database --groups=file --groups=console
with this config.yml
:
phpunit_arguments: -d xdebug.mode=off --columns=115 --number=1234
will output this:
PHP 7.2.24-0ubuntu0.18.04.7
phpunit_arguments:
Array
(
[0] => -d
[1] => xdebug.mode=off
[2] => --columns=115
[3] => --number=1234
)
argv:
Array
(
[0] => -d
[1] => xdebug.mode=on
[2] => --columns=95
[3] => --debug
[4] => --groups=database
[5] => --groups=file
[6] => --groups=console
)
all_params:
Array
(
[0] => -d
[1] => xdebug.mode=off
[2] => --columns=115
[3] => --number=1234
[4] => -d
[5] => xdebug.mode=on
[6] => --columns=95
[7] => --debug
[8] => --groups=database
[9] => --groups=file
[10] => --groups=console
)
merged:
Array
(
[0] => -d
[1] => xdebug.mode=on
[2] => --columns=95
[3] => --number=1234
[4] => --debug
[5] => --groups=console
)
cli args:
-d xdebug.mode=on --columns=95 --number=1234 --debug --groups=console
So we can see that the arguments -d xdebug.mode=off
and --columns=115
have been merged and override be the CLI arguments -d xdebug.mode=on --columns=95
and also we have only the last one --groups=console
set.
You can check this parsing working online onto this repl.it.