合并两串 CLI 选项

Merge two string of CLI options

如何合并两个包含常规 CLI 选项的字符串(即任何 getopt() 都可以正确解析。)?

第一个来自配置文件,第二个来自 Symfony 的 Console\Input\getArgument()。我还可以从 $GLOBALS['argv'] 得到第二个。我想把它们结合起来,这样我就可以用它们启动另一个程序了。

任一字符串都可以包含短选项、长选项,有值或无值。

例子

例如,config.yml 包含

phpunit_arguments: -d xdebug.mode=off --columns=115

...然后用户可以用php {filename} --columns=95 --debug调用我的程序。我想合并这些字符串,所以我最终得到:

-d xdebug.mode=off --columns=95 --debug

第一个字符串的 columns 被第二个字符串覆盖。

尝试 1 失败:转换为数组

如果我可以像下面这样将这些字符串放入数组中,那么我就可以使用 array_merge_recursive():

array(
    '-d xdebug.mode' => 'off',
    '--columns'      => 115
)

array(
     '--columns' => 95,
     '--debug'
)

...但要做到这一点,我需要一个理解 CLI 选项的解析器。

我查看了以下内容,但 none 似乎被设计为采用任意字符串和 return 已解析的数组。

失败的尝试 2:连接

我试过只是连接两个字符串而不是合并它们,这在技术上是可行的,但它会产生用户体验问题。我的程序向用户显示参数,并且连接的字符串将包含重复项,这会让一些人感到困惑。该程序还在 运行 时接受输入,并重新生成选项;随着时间的推移,附加到先前的字符串会滚雪球,更糟的是 confusion/UX.

例如,设置groups几次后,最终会变成

-d xdebug.mode=off --columns=95 --debug --groups=database --groups=file --groups=console

您可以创建自己的函数,它将智能 合并配置参数和 CLI 参数。使用单个正则表达式,我们可以提取 --- 等前缀、命令名称和具有值的相等字符。

使用正则表达式和循环合并参数

请阅读内联评论

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

结果

运行执行此命令:

php main.php -d xdebug.mode=on --columns=95 --debug --groups=database --groups=file --groups=console

用这个 config.yml:

phpunit_arguments: -d xdebug.mode=off --columns=115 --number=1234

将输出:

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

所以我们可以看到参数 -d xdebug.mode=off--columns=115 已经合并并覆盖为 CLI 参数 -d xdebug.mode=on --columns=95 而且我们只有最后一个 --groups=console设置。

运行这

您可以在线检查此解析工作repl.it