class SynopsisParser {
- private static $patterns = array();
-
/**
- * @param string
+ * @param string A synopsis
* @return array List of parameters
*/
static function parse( $synopsis ) {
- if ( empty( self::$patterns ) )
- self::init_patterns();
-
$tokens = array_filter( preg_split( '/[\s\t]+/', $synopsis ) );
$params = array();
-
foreach ( $tokens as $token ) {
- $type = false;
-
- foreach ( self::$patterns as $regex => $desc ) {
- if ( preg_match( $regex, $token, $matches ) ) {
- $type = $desc['type'];
- $params[] = array_merge( $matches, $desc );
- break;
+ $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';
}
}
- if ( !$type ) {
- $params[] = array(
- 'type' => 'unknown',
- 'token' => $token
- );
- }
+ $param['token'] = $token;
+ $params[] = $param;
}
return $params;
}
- private static function init_patterns() {
- $p_name = '(?P<name>[a-z-_]+)';
- $p_value = '(?P<value>[a-zA-Z-|]+)';
+ private static function classify_token( $token ) {
+ $param = array();
- self::gen_patterns( 'positional', "<$p_value>", array( 'mandatory', 'optional', 'repeating' ) );
- self::gen_patterns( 'generic', "--<field>=<value>", array( 'mandatory', 'optional', 'repeating' ) );
- self::gen_patterns( 'assoc', "--$p_name=<$p_value>", array( 'mandatory', 'optional' ) );
- self::gen_patterns( 'flag', "--$p_name", array( 'optional' ) );
- }
+ list( $param['optional'], $token ) = self::is_optional( $token );
+ list( $param['repeating'], $token ) = self::is_repeating( $token );
- private static function gen_patterns( $type, $pattern, $flavour_types ) {
- static $flavours = array(
- 'mandatory' => ':pattern:',
- 'optional' => '\[:pattern:\]',
- 'repeating' => array( ':pattern:...', '\[:pattern:...\]' )
- );
-
- foreach ( $flavour_types as $flavour_type ) {
- foreach ( (array) $flavours[ $flavour_type ] as $flavour ) {
- $final_pattern = str_replace( ':pattern:', $pattern, $flavour );
-
- self::$patterns[ '/^' . $final_pattern . '$/' ] = array(
- 'type' => $type,
- 'flavour' => $flavour_type
- );
+ $p_name = '([a-z-_]+)';
+ $p_value = '([a-zA-Z-|]+)';
+
+ if ( '--<field>=<value>' === $token ) {
+ $param['type'] = 'generic';
+ } elseif ( preg_match( "/^<$p_name>$/", $token, $matches ) ) {
+ $param['type'] = 'positional';
+ } elseif ( preg_match( "/^--$p_name/", $token, $matches ) ) {
+ $param['name'] = $matches[1];
+
+ $value = substr( $token, strlen( $matches[0] ) );
+
+ if ( false === $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 = array( 'type' => 'unknown' );
+ }
}
+ } else {
+ $param['type'] = 'unknown';
+ }
+
+ return $param;
+ }
+
+ /**
+ * An optional parameter is surrounded by square brackets.
+ */
+ private static function is_optional( $token ) {
+ if ( '[' == substr( $token, 0, 1 ) && ']' == substr( $token, -1 ) ) {
+ return array( true, substr( $token, 1, -1 ) );
+ } else {
+ return array( false, $token );
+ }
+ }
+
+ /**
+ * A repeating parameter is followed by an ellipsis.
+ */
+ private static function is_repeating( $token ) {
+ if ( '...' === substr( $token, -3 ) ) {
+ return array( true, substr( $token, 0, -3 ) );
+ } else {
+ return array( false, $token );
}
}
}
$this->assertCount( 2, $r );
- $this->assertEquals( 'positional', $r[0]['type'] );
- $this->assertEquals( 'mandatory', $r[0]['flavour'] );
+ $param = $r[0];
+ $this->assertEquals( 'positional', $param['type'] );
+ $this->assertFalse( $param['optional'] );
- $this->assertEquals( 'positional', $r[1]['type'] );
- $this->assertEquals( 'optional', $r[1]['flavour'] );
+ $param = $r[1];
+ $this->assertEquals( 'positional', $param['type'] );
+ $this->assertTrue( $param['optional'] );
}
function testFlag() {
$r = SynopsisParser::parse( '[--foo]' );
$this->assertCount( 1, $r );
- $this->assertEquals( 'flag', $r[0]['type'] );
- $this->assertEquals( 'optional', $r[0]['flavour'] );
+
+ $param = $r[0];
+ $this->assertEquals( 'flag', $param['type'] );
+ $this->assertTrue( $param['optional'] );
// flags can't be mandatory
$r = SynopsisParser::parse( '--foo' );
$this->assertCount( 1, $r );
- $this->assertEquals( 'unknown', $r[0]['type'] );
+
+ $param = $r[0];
+ $this->assertEquals( 'unknown', $param['type'] );
}
function testGeneric() {
- $r = SynopsisParser::parse( '--<field>=<value> [--<field>=<value>]' );
+ $r = SynopsisParser::parse( '--<field>=<value> [--<field>=<value>] --<field>[=<value>] [--<field>[=<value>]]' );
- $this->assertCount( 2, $r );
+ $this->assertCount( 4, $r );
- $this->assertEquals( 'generic', $r[0]['type'] );
- $this->assertEquals( 'mandatory', $r[0]['flavour'] );
+ $param = $r[0];
+ $this->assertEquals( 'generic', $param['type'] );
+ $this->assertFalse( $param['optional'] );
- $this->assertEquals( 'generic', $r[1]['type'] );
- $this->assertEquals( 'optional', $r[1]['flavour'] );
+ $param = $r[1];
+ $this->assertEquals( 'generic', $param['type'] );
+ $this->assertTrue( $param['optional'] );
+
+ $param = $r[2];
+ $this->assertEquals( 'unknown', $param['type'] );
+
+ $param = $r[3];
+ $this->assertEquals( 'unknown', $param['type'] );
}
function testAssoc() {
- $r = SynopsisParser::parse( '--foo=<value> [--bar=<value>]' );
+ $r = SynopsisParser::parse( '--foo=<value> [--bar=<value>] [--bar[=<value>]]' );
- $this->assertCount( 2, $r );
+ $this->assertCount( 3, $r );
- $this->assertEquals( 'assoc', $r[0]['type'] );
- $this->assertEquals( 'mandatory', $r[0]['flavour'] );
+ $param = $r[0];
+ $this->assertEquals( 'assoc', $param['type'] );
+ $this->assertFalse( $param['optional'] );
- $this->assertEquals( 'assoc', $r[1]['type'] );
- $this->assertEquals( 'optional', $r[1]['flavour'] );
+ $param = $r[1];
+ $this->assertEquals( 'assoc', $param['type'] );
+ $this->assertTrue( $param['optional'] );
+
+ $param = $r[2];
+ $this->assertEquals( 'assoc', $param['type'] );
+ $this->assertTrue( $param['optional'] );
+ $this->assertTrue( $param['value']['optional'] );
+ }
+
+ function testInvalidAssoc() {
+ $r = SynopsisParser::parse( '--bar[=<value>] --bar=[<value>] --count=100' );
+
+ $this->assertCount( 3, $r );
- // shouldn't pass defaults to assoc parameters
- $r = SynopsisParser::parse( '--count=100' );
- $this->assertCount( 1, $r );
$this->assertEquals( 'unknown', $r[0]['type'] );
+ $this->assertEquals( 'unknown', $r[1]['type'] );
+ $this->assertEquals( 'unknown', $r[2]['type'] );
}
function testRepeating() {
$this->assertCount( 2, $r );
- $this->assertEquals( 'positional', $r[0]['type'] );
- $this->assertEquals( 'repeating', $r[0]['flavour'] );
+ $param = $r[0];
+ $this->assertEquals( 'positional', $param['type'] );
+ $this->assertTrue( $param['repeating'] );
- $this->assertEquals( 'generic', $r[1]['type'] );
- $this->assertEquals( 'repeating', $r[1]['flavour'] );
+ $param = $r[1];
+ $this->assertEquals( 'generic', $param['type'] );
+ $this->assertTrue( $param['repeating'] );
}
function testCombined() {
$r = SynopsisParser::parse( '<positional> --assoc=<someval> --<field>=<value> [--flag]' );
+ $this->assertCount( 4, $r );
+
$this->assertEquals( 'positional', $r[0]['type'] );
$this->assertEquals( 'assoc', $r[1]['type'] );
$this->assertEquals( 'generic', $r[2]['type'] );
$this->assertEquals( 'flag', $r[3]['type'] );
}
- function testAllowedValueCharacters() {
- $r = SynopsisParser::parse( '--capitals=<VALUE> --hyphen=<val-ue> --combined=<VAL-ue> --disallowed=<wrong:char>' );
+ function testAllowedValueCharacters() {
+ $r = SynopsisParser::parse( '--capitals=<VALUE> --hyphen=<val-ue> --combined=<VAL-ue> --disallowed=<wrong:char>' );
- $this->assertCount( 4, $r );
+ $this->assertCount( 4, $r );
- $this->assertEquals( 'assoc', $r[0]['type'] );
- $this->assertEquals( 'mandatory', $r[0]['flavour'] );
+ $param = $r[0];
+ $this->assertEquals( 'assoc', $param['type'] );
+ $this->assertFalse( $param['optional'] );
- $this->assertEquals( 'assoc', $r[1]['type'] );
- $this->assertEquals( 'mandatory', $r[1]['flavour'] );
+ $param = $r[1];
+ $this->assertEquals( 'assoc', $param['type'] );
+ $this->assertFalse( $param['optional'] );
- $this->assertEquals( 'assoc', $r[2]['type'] );
- $this->assertEquals( 'mandatory', $r[2]['flavour'] );
-
- $this->assertEquals( 'unknown', $r[3]['type'] );
- }
+ $param = $r[2];
+ $this->assertEquals( 'assoc', $param['type'] );
+ $this->assertFalse( $param['optional'] );
+ $this->assertEquals( 'unknown', $r[3]['type'] );
+ }
}