File: //usr/local/wp/php/WP_CLI/SynopsisParser.php
<?php
namespace WP_CLI;
/**
* Generate a synopsis from a command's PHPdoc arguments.
* Turns something like "<object-id>..."
* into [ optional=>false, type=>positional, repeating=>true, name=>object-id ]
*/
class SynopsisParser {
/**
* @param string $synopsis A synopsis
* @return array List of parameters
*/
public static function parse( $synopsis ) {
$tokens = array_filter( preg_split( '/[\s\t]+/', $synopsis ) );
$params = [];
foreach ( $tokens as $token ) {
$param = self::classify_token( $token );
// Some types of parameters shouldn't be mandatory
if ( isset( $param['optional'] ) && ! $param['optional'] ) {
if ( 'flag' === $param['type'] || ( 'assoc' === $param['type'] && $param['value']['optional'] ) ) {
$param['type'] = 'unknown';
}
}
$param['token'] = $token;
$params[] = $param;
}
return $params;
}
/**
* Render the Synopsis into a format string.
*
* @param array $synopsis A structured synopsis. This might get reordered
* to match the parsed output.
* @return string Rendered synopsis.
*/
public static function render( &$synopsis ) {
if ( ! is_array( $synopsis ) ) {
return '';
}
$bits = [
'positional' => '',
'assoc' => '',
'generic' => '',
'flag' => '',
];
$reordered_synopsis = [
'positional' => [],
'assoc' => [],
'generic' => [],
'flag' => [],
];
foreach ( $bits as $key => &$value ) {
foreach ( $synopsis as $arg ) {
if ( empty( $arg['type'] )
|| $key !== $arg['type'] ) {
continue;
}
if ( empty( $arg['name'] ) && 'generic' !== $arg['type'] ) {
continue;
}
if ( 'positional' === $key ) {
$rendered_arg = "<{$arg['name']}>";
$reordered_synopsis['positional'] [] = $arg;
} elseif ( 'assoc' === $key ) {
$arg_value = isset( $arg['value']['name'] ) ? $arg['value']['name'] : $arg['name'];
$arg_value = "=<{$arg_value}>";
if ( ! empty( $arg['value']['optional'] ) ) {
$arg_value = "[{$arg_value}]";
}
$rendered_arg = "--{$arg['name']}{$arg_value}";
$reordered_synopsis['assoc'] [] = $arg;
} elseif ( 'generic' === $key ) {
$rendered_arg = '--<field>=<value>';
$reordered_synopsis['generic'] [] = $arg;
} elseif ( 'flag' === $key ) {
$rendered_arg = "--{$arg['name']}";
$reordered_synopsis['flag'] [] = $arg;
}
if ( ! empty( $arg['repeating'] ) ) {
$rendered_arg = "{$rendered_arg}...";
}
if ( ! empty( $arg['optional'] ) ) {
$rendered_arg = "[{$rendered_arg}]";
}
$value .= "{$rendered_arg} ";
}
}
$rendered = implode( '', $bits );
$synopsis = array_merge(
$reordered_synopsis['positional'],
$reordered_synopsis['assoc'],
$reordered_synopsis['generic'],
$reordered_synopsis['flag']
);
return rtrim( $rendered, ' ' );
}
/**
* Classify argument attributes based on its syntax.
*
* @param string $token
* @return array
*/
private static function classify_token( $token ) {
$param = [];
list( $param['optional'], $token ) = self::is_optional( $token );
list( $param['repeating'], $token ) = self::is_repeating( $token );
$p_name = '([a-z-_0-9]+)';
$p_value = '([a-zA-Z-_|,0-9]+)';
if ( '--<field>=<value>' === $token ) {
$param['type'] = 'generic';
} elseif ( preg_match( "/^<($p_value)>$/", $token, $matches ) ) {
$param['type'] = 'positional';
$param['name'] = $matches[1];
} elseif ( preg_match( "/^--(?:\\[no-\\])?$p_name/", $token, $matches ) ) {
$param['name'] = $matches[1];
$value = substr( $token, strlen( $matches[0] ) );
// substr returns false <= PHP 5.6, and '' PHP 7+
if ( false === $value || '' === $value ) {
$param['type'] = 'flag';
} else {
$param['type'] = 'assoc';
list( $param['value']['optional'], $value ) = self::is_optional( $value );
if ( preg_match( "/^=<$p_value>$/", $value, $matches ) ) {
$param['value']['name'] = $matches[1];
} else {
$param = [
'type' => 'unknown',
];
}
}
} else {
$param['type'] = 'unknown';
}
return $param;
}
/**
* An optional parameter is surrounded by square brackets.
*
* @param string $token
* @return array
*/
private static function is_optional( $token ) {
if ( '[' === substr( $token, 0, 1 ) && ']' === substr( $token, -1 ) ) {
return [ true, substr( $token, 1, -1 ) ];
}
return [ false, $token ];
}
/**
* A repeating parameter is followed by an ellipsis.
*
* @param string $token
* @return array
*/
private static function is_repeating( $token ) {
if ( '...' === substr( $token, -3 ) ) {
return [ true, substr( $token, 0, -3 ) ];
}
return [ false, $token ];
}
}