Skip to content

Handle quoted multi-word argument & option values #21

Open

Description

@tuaris

Not sure if I'm "doing it wrong", but I am having the same issue as reported in other languages, where Docopt does not treat quote delimited strings as one parameter.

As demonstrated here

Also reported in:

docopt/docopt#207
docopt/try.docopt.org#3
docopt/docopt.R#4

Until this is fixed I suggest putting a warning or removing examples like git commit where it's typical for a command like git commit -m "To have multiple words inside a quote".

In the mean time I have created a workaround that isn't perfect but has so far worked okay for me. I pre-process and post-process the input/output.

The pre-process function replaces any quoted strings with a variable so that git commit -m "To have multiple words inside a quote" becomes git commit -m $_v12345abcdef.

function pre_processor(string $string) {
	$variables = [];

	$new_string = preg_replace_callback('/["\'](.*?(?<!\\\\))["\']/m', function($match) use (&$variables) {
		// Results in a var name like 'v12345abcdef'
		$varName = 'v' . hash('fnv164', $match[0]);
		$variables[$varName] = $match[1];
		// Results in a placeholder like $_v12345abcdef
		return '$_' . $varName;
	},$string);

	return ['string' => $new_string, 'variables' => $variables];
}

The $new_string is the passed to Docopt handler in place of the normal CLI input args. The result of which is then sent to a post processing function to fill the variables back in

function post_processor(array $args, array $variables) {
	foreach ($args as &$arg){
		// find any thing that matches like $_v12345abcdef and replace it with the actual value
		$arg = preg_replace_callback('/\$_(v[a-f0-9]{16})\b/', function($match) use ($variables) {
			return str_replace($match[0], isset($variables[$match[1]]) ? $variables[$match[1]] : $match[0], $match[0]);
		}, $arg);
	}
	
	return $args;
}

For example, you might have something that looks like this:

function handle($string) {
	list('string' => $processed_string, 'variables' => $variables) = pre_processor($string);
	$result = $DOCOPT->handle($doc, $processed_string);	
	$real_args = post_processor($result->args, $variables);
	return $real_args;
}

I haven't looked through the Docopt.php code, but this might be something that could be done as part of the existing handle() function.

Activity

  1. shabbyrobe commented on Sep 8, 2019

    @shabbyrobe
    Member

    Hi tuaris, thanks for reporting.

    The PHP port of docopt is a pretty straight transliteration of the Python code. It was done this way to make integrating upstream patches easier and to ensure consistency between the implementations.

    Unfortunately, that means that until the issue is fixed in the Python version, the PHP version will be stuck with this behaviour. I think it makes sense to update the README here to indicate this is a known issue though. I'll keep this issue open so that it's known that this is a problem, but I'll label it as "NeedsUpstreamFix" or somesuch.

  2. JKingweb commented on Dec 26, 2023

    @JKingweb
    Contributor

    This report is a little misleading. Docopt will only act incorrectly if the input command is provided as a string (docopt then does naïve splitting). If the input is an alread-split array, the bug does not manifest.

    If you're just using $argv anyway, providing that array is the best option. If your input is a string for whatever reason, you can use a dedicated argument tokenizer to produce an argument array for docopt to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions