-
-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Regex] Introduce matching() function
- Loading branch information
Showing
5 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Regex\Type; | ||
|
||
use Psl\Type\TypeInterface; | ||
|
||
use function Psl\Dict\from_keys; | ||
use function Psl\Dict\unique; | ||
use function Psl\Type\shape; | ||
use function Psl\Type\string; | ||
|
||
/** | ||
* @param list<array-key> $groups | ||
* | ||
* @return TypeInterface<array<array-key, string>> | ||
* | ||
* @psalm-suppress MixedReturnTypeCoercion - Psalm uses track of the types: Trust the return type above. | ||
*/ | ||
function capture_groups(array $groups): TypeInterface | ||
{ | ||
return shape( | ||
from_keys( | ||
unique([0, ...$groups]), | ||
/** | ||
* @return TypeInterface<string> | ||
*/ | ||
static fn() => string() | ||
) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Regex; | ||
|
||
use Psl\Regex\Exception\RuntimeException; | ||
use Psl\Type\Exception\CoercionException; | ||
use Psl\Type\TypeInterface; | ||
|
||
use function preg_match; | ||
|
||
/** | ||
* Determine if $subject matches the given $pattern and return the matches. | ||
* | ||
* @template T of array | ||
* | ||
* @param non-empty-string $pattern The pattern to match against. | ||
* @param TypeInterface<T> $capture_groups What shape does the matching items have? | ||
* | ||
* @return T | ||
* | ||
* @throws Exception\RuntimeException If an internal error accord. | ||
* @throws CoercionException If the capture groups don't match the pattern matches | ||
* @throws Exception\InvalidPatternException If $pattern is invalid. | ||
*/ | ||
function matching(string $subject, string $pattern, TypeInterface $capture_groups, int $offset = 0): array | ||
{ | ||
$matching = Internal\call_preg( | ||
'preg_match', | ||
static function () use ($subject, $pattern, $offset): array { | ||
$matching = []; | ||
$matches = preg_match($pattern, $subject, $matching, 0, $offset); | ||
|
||
if ($matches === 0) { | ||
throw new RuntimeException( | ||
'Could not match the pattern ' . $pattern . ' to the given input: ' . $subject . '.' | ||
); | ||
} | ||
|
||
return $matching; | ||
} | ||
); | ||
|
||
return $capture_groups->coerce($matching); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Tests\Regex; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Psl\Regex; | ||
use Psl\Type\TypeInterface; | ||
|
||
use function Psl\Regex\Type\capture_groups; | ||
|
||
final class MatchingTest extends TestCase | ||
{ | ||
/** | ||
* @dataProvider provideMatchingData | ||
*/ | ||
public function testMatching( | ||
array $expected, | ||
string $subject, | ||
string $pattern, | ||
TypeInterface $shape, | ||
int $offset = 0 | ||
): void { | ||
static::assertSame($expected, Regex\matching($subject, $pattern, $shape, $offset)); | ||
} | ||
|
||
/** | ||
* @dataProvider provideNonMatchingData | ||
*/ | ||
public function testNotMatching(string $subject, string $pattern, int $offset = 0) | ||
{ | ||
$this->expectException(Regex\Exception\RuntimeException::class); | ||
$this->expectErrorMessage('Could not match the pattern ' . $pattern . ' to the given input: ' . $subject . '.'); | ||
|
||
Regex\matching($subject, $pattern, capture_groups([]), $offset); | ||
} | ||
|
||
public function testMatchingWithInvalidPattern(): void | ||
{ | ||
$this->expectException(Regex\Exception\InvalidPatternException::class); | ||
$this->expectExceptionMessage("No ending delimiter '/' found"); | ||
|
||
Regex\matching('hello', '/hello', capture_groups([])); | ||
} | ||
|
||
public function provideMatchingData(): iterable | ||
{ | ||
yield [ | ||
[ | ||
0 => 'PHP', | ||
1 => 'PHP', | ||
], | ||
'PHP is the web scripting language of choice.', | ||
'/(php)/i', | ||
capture_groups([0, 1]) | ||
]; | ||
yield [ | ||
[ | ||
0 => 'Hello world', | ||
1 => 'Hello', | ||
], | ||
'Hello world is the web scripting language of choice.', | ||
'/(hello) world/i', | ||
capture_groups([0, 1]) | ||
]; | ||
yield [ | ||
[ | ||
0 => 'web', | ||
1 => 'web', | ||
], | ||
'PHP is the web scripting language of choice.', | ||
'/(\bweb\b)/i', | ||
capture_groups([0, 1]) | ||
]; | ||
yield [ | ||
[ | ||
0 => 'PHP', | ||
'language' => 'PHP', | ||
], | ||
'PHP is the web scripting language of choice.', | ||
'/(?P<language>PHP)/', | ||
capture_groups([0, 'language']) | ||
]; | ||
yield [ | ||
[ | ||
0 => 'http://www.php.net', | ||
1 => 'www.php.net' | ||
], | ||
'http://www.php.net/index.html', | ||
'@^(?:http://)?([^/]+)@i', | ||
capture_groups([1]) | ||
]; | ||
} | ||
|
||
public function provideNonMatchingData(): iterable | ||
{ | ||
yield ['PHP is the web scripting language of choice.', '/php/']; | ||
yield ['PHP is the website scripting language of choice.', '/\bweb\b/i']; | ||
yield ['php is the web scripting language of choice.', '/PHP/']; | ||
yield ['hello', '/[^.]+\.[^.]+$/']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Regex\Type; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
|
||
final class CaptureGroupsTest extends TestCase | ||
{ | ||
|
||
public function testItAlwaysAddsZeroCaptureResult(): void | ||
{ | ||
$data = [0 => 'Hello', 1 => 'World']; | ||
$shape = capture_groups([1]); | ||
$actual = $shape->coerce($data); | ||
|
||
static::assertSame($actual, $data); | ||
} | ||
} |