/** * Interactively prompt the user for input * based on defined synopsis and passed arguments. * * @param array $args * @param array $assoc_args * @return array */ private function prompt_args($args, $assoc_args) { $synopsis = $this->get_synopsis(); if (!$synopsis) { return array($args, $assoc_args); } $spec = array_filter(\WP_CLI\SynopsisParser::parse($synopsis), function ($spec_arg) { return in_array($spec_arg['type'], array('generic', 'positional', 'assoc', 'flag')); }); $spec = array_values($spec); // 'positional' arguments are positional (aka zero-indexed) // so $args needs to be reset before prompting for new arguments $args = array(); foreach ($spec as $key => $spec_arg) { $current_prompt = $key + 1 . '/' . count($spec) . ' '; $default = $spec_arg['optional'] ? '' : false; // 'generic' permits arbitrary key=value (e.g. [--<field>=<value>] ) if ('generic' == $spec_arg['type']) { list($key_token, $value_token) = explode('=', $spec_arg['token']); $repeat = false; do { if (!$repeat) { $key_prompt = $current_prompt . $key_token; } else { $key_prompt = str_repeat(" ", strlen($current_prompt)) . $key_token; } $key = $this->prompt($key_prompt, $default); if (false === $key) { return array($args, $assoc_args); } if ($key) { $key_prompt_count = strlen($key_prompt) - strlen($value_token) - 1; $value_prompt = str_repeat(" ", $key_prompt_count) . '=' . $value_token; $value = $this->prompt($value_prompt, $default); if (false === $value) { return array($args, $assoc_args); } $assoc_args[$key] = $value; $repeat = true; $required = false; } else { $repeat = false; } } while ($required || $repeat); } else { $prompt = $current_prompt . $spec_arg['token']; if ('flag' == $spec_arg['type']) { $prompt .= ' (Y/n)'; } $response = $this->prompt($prompt, $default); if (false === $response) { return array($args, $assoc_args); } if ($response) { switch ($spec_arg['type']) { case 'positional': if ($spec_arg['repeating']) { $response = explode(' ', $response); } else { $response = array($response); } $args = array_merge($args, $response); break; case 'assoc': $assoc_args[$spec_arg['name']] = $response; break; case 'flag': if ('Y' == $response) { $assoc_args[$spec_arg['name']] = true; } break; } } } } return array($args, $assoc_args); }
/** * Validate the supplied arguments to the command. * Throws warnings or errors if arguments are missing * or invalid. * * @param array $args * @param array $assoc_args * @param array $extra_args * @return array list of invalid $assoc_args keys to unset */ private function validate_args($args, $assoc_args, $extra_args) { $synopsis = $this->get_synopsis(); if (!$synopsis) { return array(array(), $args, $assoc_args, $extra_args); } $validator = new \WP_CLI\SynopsisValidator($synopsis); $cmd_path = implode(' ', get_path($this)); foreach ($validator->get_unknown() as $token) { \WP_CLI::warning(sprintf("The `%s` command has an invalid synopsis part: %s", $cmd_path, $token)); } if (!$validator->enough_positionals($args)) { $this->show_usage(); exit(1); } $unknown_positionals = $validator->unknown_positionals($args); if (!empty($unknown_positionals)) { \WP_CLI::error('Too many positional arguments: ' . implode(' ', $unknown_positionals)); } $synopsis_spec = \WP_CLI\SynopsisParser::parse($synopsis); $i = 0; $errors = array('fatal' => array(), 'warning' => array()); foreach ($synopsis_spec as $spec) { if ('positional' === $spec['type']) { $spec_args = $this->docparser->get_arg_args($spec['name']); if (!isset($args[$i])) { if (isset($spec_args['default'])) { $args[$i] = $spec_args['default']; } } if (isset($spec_args['options'])) { if (!empty($spec['repeating'])) { do { if (isset($args[$i]) && !in_array($args[$i], $spec_args['options'])) { \WP_CLI::error('Invalid value specified for positional arg.'); } $i++; } while (isset($args[$i])); } else { if (!in_array($args[$i], $spec_args['options'])) { \WP_CLI::error('Invalid value specified for positional arg.'); } } } $i++; } else { if ('assoc' === $spec['type']) { $spec_args = $this->docparser->get_param_args($spec['name']); if (!isset($assoc_args[$spec['name']])) { if (isset($spec_args['default'])) { $assoc_args[$spec['name']] = $spec_args['default']; } } if (isset($assoc_args[$spec['name']]) && isset($spec_args['options'])) { if (!in_array($assoc_args[$spec['name']], $spec_args['options'])) { $errors['fatal'][$spec['name']] = "Invalid value specified for '{$spec['name']}'"; } } } } } list($returned_errors, $to_unset) = $validator->validate_assoc(array_merge(\WP_CLI::get_config(), $extra_args, $assoc_args)); foreach (array('fatal', 'warning') as $error_type) { $errors[$error_type] = array_merge($errors[$error_type], $returned_errors[$error_type]); } if ($this->name != 'help') { foreach ($validator->unknown_assoc($assoc_args) as $key) { $errors['fatal'][] = "unknown --{$key} parameter"; } } if (!empty($errors['fatal'])) { $out = 'Parameter errors:'; foreach ($errors['fatal'] as $key => $error) { $out .= "\n {$error}"; if ($desc = $this->docparser->get_param_desc($key)) { $out .= " ({$desc})"; } } \WP_CLI::error($out); } array_map('\\WP_CLI::warning', $errors['warning']); return array($to_unset, $args, $assoc_args, $extra_args); }
/** * Add a command to the wp-cli list of commands * * @param string $name The name of the command that will be used in the CLI * @param string $callable The command implementation as a class, function or closure * @param array $args An associative array with additional parameters: * 'before_invoke' => callback to execute before invoking the command, * 'shortdesc' => short description (80 char or less) for the command, * 'synopsis' => the synopsis for the command (string or array) * 'when' => execute callback on a named WP-CLI hook (e.g. before_wp_load) */ public static function add_command($name, $callable, $args = array()) { $valid = false; if (is_object($callable) && $callable instanceof \Closure) { $valid = true; } else { if (is_string($callable) && function_exists($callable)) { $valid = true; } else { if (is_string($callable) && class_exists((string) $callable)) { $valid = true; } else { if (is_object($callable)) { $valid = true; } else { if (is_array($callable) && is_callable($callable)) { $valid = true; } } } } } if (!$valid) { if (is_array($callable)) { $callable[0] = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; $callable = array($callable[0], $callable[1]); } WP_CLI::error(sprintf("Callable %s does not exist, and cannot be registered as `wp %s`.", json_encode($callable), $name)); } if (isset($args['before_invoke'])) { self::add_hook("before_invoke:{$name}", $args['before_invoke']); } $path = preg_split('/\\s+/', $name); $leaf_name = array_pop($path); $full_path = $path; $command = self::get_root_command(); while (!empty($path)) { $subcommand_name = $path[0]; $subcommand = $command->find_subcommand($path); // create an empty container if (!$subcommand) { $subcommand = new Dispatcher\CompositeCommand($command, $subcommand_name, new \WP_CLI\DocParser('')); $command->add_subcommand($subcommand_name, $subcommand); } $command = $subcommand; } $leaf_command = Dispatcher\CommandFactory::create($leaf_name, $callable, $command); if (!$command->can_have_subcommands()) { throw new Exception(sprintf("'%s' can't have subcommands.", implode(' ', Dispatcher\get_path($command)))); } if (isset($args['shortdesc'])) { $leaf_command->set_shortdesc($args['shortdesc']); } if (isset($args['synopsis'])) { if (is_string($args['synopsis'])) { $leaf_command->set_synopsis($args['synopsis']); } else { if (is_array($args['synopsis'])) { $leaf_command->set_synopsis(\WP_CLI\SynopsisParser::render($args['synopsis'])); } } } if (isset($args['when'])) { self::get_runner()->register_early_invoke($args['when'], $leaf_command); } $command->add_subcommand($leaf_name, $leaf_command); }
function testAllowedValueCharacters() { $r = SynopsisParser::parse('--capitals=<VALUE> --hyphen=<val-ue> --combined=<VAL-ue> --disallowed=<wrong:char>'); $this->assertCount(4, $r); $param = $r[0]; $this->assertEquals('assoc', $param['type']); $this->assertFalse($param['optional']); $param = $r[1]; $this->assertEquals('assoc', $param['type']); $this->assertFalse($param['optional']); $param = $r[2]; $this->assertEquals('assoc', $param['type']); $this->assertFalse($param['optional']); $this->assertEquals('unknown', $r[3]['type']); }
function testParseThenRender() { $o = '<positional> --assoc=<assoc> --<field>=<value> [--flag]'; $a = SynopsisParser::parse($o); $r = SynopsisParser::render($a); $this->assertEquals($o, $r); }
/** * Register a command to WP-CLI. * * WP-CLI supports using any callable class, function, or closure as a * command. `WP_CLI::add_command()` is used for both internal and * third-party command registration. * * Command arguments are parsed from PHPDoc by default, but also can be * supplied as an optional third argument during registration. * * ``` * # Register a custom 'foo' command to output a supplied positional param. * # * # $ wp foo bar * # Success: bar * * /** * * My awesome closure command * * * * <message> * * : An awesome message to display * * * * @when before_wp_load * *\/ * $foo = function( $args ) { * WP_CLI::success( $args[0] ); * }; * WP_CLI::add_command( 'foo', $foo ); * ``` * * @access public * @category Registration * * @param string $name Name for the command (e.g. "post list" or "site empty"). * @param string $callable Command implementation as a class, function or closure. * @param array $args { * Optional An associative array with additional registration parameters. * 'before_invoke' => callback to execute before invoking the command, * 'after_invoke' => callback to execute after invoking the command, * 'shortdesc' => short description (80 char or less) for the command, * 'synopsis' => the synopsis for the command (string or array), * 'when' => execute callback on a named WP-CLI hook (e.g. before_wp_load), * } * @return true True on success, hard error if registration failed. */ public static function add_command($name, $callable, $args = array()) { $valid = false; if (is_callable($callable)) { $valid = true; } else { if (is_string($callable) && class_exists((string) $callable)) { $valid = true; } else { if (is_object($callable)) { $valid = true; } } } if (!$valid) { if (is_array($callable)) { $callable[0] = is_object($callable[0]) ? get_class($callable[0]) : $callable[0]; $callable = array($callable[0], $callable[1]); } WP_CLI::error(sprintf("Callable %s does not exist, and cannot be registered as `wp %s`.", json_encode($callable), $name)); } foreach (array('before_invoke', 'after_invoke') as $when) { if (isset($args[$when])) { self::add_hook("{$when}:{$name}", $args[$when]); } } $path = preg_split('/\\s+/', $name); $leaf_name = array_pop($path); $full_path = $path; $command = self::get_root_command(); while (!empty($path)) { $subcommand_name = $path[0]; $subcommand = $command->find_subcommand($path); // create an empty container if (!$subcommand) { $subcommand = new Dispatcher\CompositeCommand($command, $subcommand_name, new \WP_CLI\DocParser('')); $command->add_subcommand($subcommand_name, $subcommand); } $command = $subcommand; } $leaf_command = Dispatcher\CommandFactory::create($leaf_name, $callable, $command); if (!$command->can_have_subcommands()) { throw new Exception(sprintf("'%s' can't have subcommands.", implode(' ', Dispatcher\get_path($command)))); } if (isset($args['shortdesc'])) { $leaf_command->set_shortdesc($args['shortdesc']); } if (isset($args['synopsis'])) { if (is_string($args['synopsis'])) { $leaf_command->set_synopsis($args['synopsis']); } else { if (is_array($args['synopsis'])) { $synopsis = \WP_CLI\SynopsisParser::render($args['synopsis']); $leaf_command->set_synopsis($synopsis); $long_desc = ''; $bits = explode(' ', $synopsis); foreach ($args['synopsis'] as $key => $arg) { $long_desc .= $bits[$key] . PHP_EOL; if (!empty($arg['description'])) { $long_desc .= ': ' . $arg['description'] . PHP_EOL; } $yamlify = array(); foreach (array('default', 'options') as $key) { if (isset($arg[$key])) { $yamlify[$key] = $arg[$key]; } } if (!empty($yamlify)) { $long_desc .= \Spyc::YAMLDump($yamlify); $long_desc .= '---' . PHP_EOL; } $long_desc .= PHP_EOL; } if (!empty($long_desc)) { $long_desc = rtrim($long_desc, PHP_EOL); $long_desc = '## OPTIONS' . PHP_EOL . PHP_EOL . $long_desc; $leaf_command->set_longdesc($long_desc); } } } } if (isset($args['when'])) { self::get_runner()->register_early_invoke($args['when'], $leaf_command); } $command->add_subcommand($leaf_name, $leaf_command); return true; }