Ian Dunn
Ian Dunn

Reputation: 3680

Merge two string of CLI options

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.

Example

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.

Failed Attempt 1: Converting to arrays

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.

Failed Attempt 2: Concatenating

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

Upvotes: 1

Views: 181

Answers (1)

Christos Lytras
Christos Lytras

Reputation: 37318

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.

Merging arguments using regular expressions and loops

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;
}

Result

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.

Run it

You can check this parsing working online onto this repl.it.

Upvotes: 3

Related Questions