-
-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(str): add range function (#385)
Signed-off-by: azjezz <azjezz@protonmail.com>
- Loading branch information
Showing
12 changed files
with
395 additions
and
1 deletion.
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
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
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,64 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Str\Byte; | ||
|
||
use Psl\Range\LowerBoundRangeInterface; | ||
use Psl\Range\RangeInterface; | ||
use Psl\Range\UpperBoundRangeInterface; | ||
use Psl\Str\Exception; | ||
|
||
/** | ||
* Slice a string using a range. | ||
* | ||
* If the range doesn't have an upper range, the slice will contain the | ||
* rest of the string. If the upper-bound is equal to the lower-bound, | ||
* then an empty string will be returned. | ||
* | ||
* Example: | ||
* | ||
* ```php | ||
* use Psl\Range; | ||
* use Psl\Str\Byte; | ||
* | ||
* $string = 'Hello, World!'; | ||
* | ||
* Byte\range($string, Range\between(0, 3, upper_inclusive: true)); // 'Hell' | ||
* Byte\range($string, Range\between(0, 3, upper_inclusive: false)); // 'Hel' | ||
* Byte\range($string, Range\from(3)); // 'lo, World!' | ||
* Byte\range($string, Range\to(3, true)); // 'Hell' | ||
* Byte\range($string, Range\to(3, false)); // 'Hel' | ||
* Byte\range($string, Range\full()); // 'Hello, World!' | ||
* Byte\range($string, Range\between(7, 5, true)); // 'World' | ||
* ``` | ||
* | ||
* @param RangeInterface<int> $range | ||
* | ||
* @throws Exception\OutOfBoundsException If the $offset is out-of-bounds. | ||
* | ||
* @pure | ||
*/ | ||
function range(string $string, RangeInterface $range): string | ||
{ | ||
$offset = 0; | ||
$length = null; | ||
if ($range instanceof LowerBoundRangeInterface) { | ||
/** @var int<0, max> $offset */ | ||
$offset = $range->getLowerBound(); | ||
} | ||
|
||
if ($range instanceof UpperBoundRangeInterface) { | ||
/** | ||
* @psalm-suppress InvalidOperand | ||
* | ||
* @var int<0, max> $length | ||
*/ | ||
$length = $range->getUpperBound() - $offset; | ||
if ($range->isUpperInclusive()) { | ||
$length += 1; | ||
} | ||
} | ||
|
||
return slice($string, $offset, $length); | ||
} |
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,65 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Str\Grapheme; | ||
|
||
use Psl\Range\LowerBoundRangeInterface; | ||
use Psl\Range\RangeInterface; | ||
use Psl\Range\UpperBoundRangeInterface; | ||
use Psl\Str\Exception; | ||
|
||
/** | ||
* Slice a string using a range. | ||
* | ||
* If the range doesn't have an upper range, the slice will contain the | ||
* rest of the string. If the upper-bound is equal to the lower-bound, | ||
* then an empty string will be returned. | ||
* | ||
* Example: | ||
* | ||
* ```php | ||
* use Psl\Range; | ||
* use Psl\Str; | ||
* | ||
* $string = 'Hello, World!'; | ||
* | ||
* Str\range($string, Range\between(0, 3, upper_inclusive: true)); // 'Hell' | ||
* Str\range($string, Range\between(0, 3, upper_inclusive: false)); // 'Hel' | ||
* Str\range($string, Range\from(3)); // 'lo, World!' | ||
* Str\range($string, Range\to(3, true)); // 'Hell' | ||
* Str\range($string, Range\to(3, false)); // 'Hel' | ||
* Str\range($string, Range\full()); // 'Hello, World!' | ||
* Str\range($string, Range\between(7, 5, true)); // 'World' | ||
* ``` | ||
* | ||
* @param RangeInterface<int> $range | ||
* | ||
* @throws Exception\OutOfBoundsException If the $offset is out-of-bounds. | ||
* @throws Exception\InvalidArgumentException If $string is not made of grapheme clusters. | ||
* | ||
* @pure | ||
*/ | ||
function range(string $string, RangeInterface $range): string | ||
{ | ||
$offset = 0; | ||
$length = null; | ||
if ($range instanceof LowerBoundRangeInterface) { | ||
/** @var int<0, max> $offset */ | ||
$offset = $range->getLowerBound(); | ||
} | ||
|
||
if ($range instanceof UpperBoundRangeInterface) { | ||
/** | ||
* @psalm-suppress InvalidOperand | ||
* | ||
* @var int<0, max> $length | ||
*/ | ||
$length = $range->getUpperBound() - $offset; | ||
if ($range->isUpperInclusive()) { | ||
$length += 1; | ||
} | ||
} | ||
|
||
return slice($string, $offset, $length); | ||
} |
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,63 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Str; | ||
|
||
use Psl\Range\LowerBoundRangeInterface; | ||
use Psl\Range\RangeInterface; | ||
use Psl\Range\UpperBoundRangeInterface; | ||
|
||
/** | ||
* Slice a string using a range. | ||
* | ||
* If the range doesn't have an upper range, the slice will contain the | ||
* rest of the string. If the upper-bound is equal to the lower-bound, | ||
* then an empty string will be returned. | ||
* | ||
* Example: | ||
* | ||
* ```php | ||
* use Psl\Range; | ||
* use Psl\Str; | ||
* | ||
* $string = 'Hello, World!'; | ||
* | ||
* Str\range($string, Range\between(0, 3, upper_inclusive: true)); // 'Hell' | ||
* Str\range($string, Range\between(0, 3, upper_inclusive: false)); // 'Hel' | ||
* Str\range($string, Range\from(3)); // 'lo, World!' | ||
* Str\range($string, Range\to(3, true)); // 'Hell' | ||
* Str\range($string, Range\to(3, false)); // 'Hel' | ||
* Str\range($string, Range\full()); // 'Hello, World!' | ||
* Str\range($string, Range\between(7, 5, true)); // 'World' | ||
* ``` | ||
* | ||
* @param RangeInterface<int> $range | ||
* | ||
* @throws Exception\OutOfBoundsException If the $offset is out-of-bounds. | ||
* | ||
* @pure | ||
*/ | ||
function range(string $string, RangeInterface $range, Encoding $encoding = Encoding::UTF_8): string | ||
{ | ||
$offset = 0; | ||
$length = null; | ||
if ($range instanceof LowerBoundRangeInterface) { | ||
/** @var int<0, max> $offset */ | ||
$offset = $range->getLowerBound(); | ||
} | ||
|
||
if ($range instanceof UpperBoundRangeInterface) { | ||
/** | ||
* @psalm-suppress InvalidOperand | ||
* | ||
* @var int<0, max> $length | ||
*/ | ||
$length = $range->getUpperBound() - $offset; | ||
if ($range->isUpperInclusive()) { | ||
$length += 1; | ||
} | ||
} | ||
|
||
return slice($string, $offset, $length, $encoding); | ||
} |
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,59 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Tests\Unit\Str\Byte; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Psl\Range; | ||
use Psl\Str\Byte; | ||
use Psl\Str\Exception; | ||
|
||
final class RangeTest extends TestCase | ||
{ | ||
/** | ||
* @param Range\RangeInterface<int> $range | ||
* | ||
* @dataProvider provideData | ||
*/ | ||
public function testRange(string $expected, string $string, Range\RangeInterface $range): void | ||
{ | ||
static::assertSame($expected, Byte\range($string, $range)); | ||
} | ||
|
||
/** | ||
* @return list<{0: string, 1: string, 2: Range\RangeInterface<int>}> | ||
*/ | ||
public function provideData(): array | ||
{ | ||
return [ | ||
['', '', Range\between(0, 5, upper_inclusive: true)], | ||
['Hello,', 'Hello, World!', Range\between(0, 5, upper_inclusive: true)], | ||
['Hello', 'Hello, World!', Range\between(0, 5, upper_inclusive: false)], | ||
['Hello, World!', 'Hello, World!', Range\from(0)], | ||
['World!', 'Hello, World!', Range\between(7, 12, upper_inclusive: true)], | ||
['World', 'Hello, World!', Range\between(7, 12, upper_inclusive: false)], | ||
['destiny', 'People linked by destiny will always find each other.', Range\between(17, 23, upper_inclusive: true)], | ||
['destiny', 'People linked by destiny will always find each other.', Range\between(17, 24, upper_inclusive: false)], | ||
['hel', 'hello world', Range\to(3, inclusive: false)], | ||
['', 'lo world', Range\between(3, 3)], | ||
['', 'foo', Range\between(3, 3)], | ||
['', 'foo', Range\between(3, 12)], | ||
['foo', 'foo', Range\full()], | ||
]; | ||
} | ||
|
||
public function testRangeThrowsForOutOfBoundOffset(): void | ||
{ | ||
$this->expectException(Exception\OutOfBoundsException::class); | ||
|
||
Byte\range('Hello', Range\from(10)); | ||
} | ||
|
||
public function testRangeThrowsForNegativeOutOfBoundOffset(): void | ||
{ | ||
$this->expectException(Exception\OutOfBoundsException::class); | ||
|
||
Byte\range('Hello', Range\from(-6)); | ||
} | ||
} |
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,67 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Psl\Tests\Unit\Str\Grapheme; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Psl\Range; | ||
use Psl\Str\Exception; | ||
use Psl\Str\Grapheme; | ||
|
||
final class RangeTest extends TestCase | ||
{ | ||
/** | ||
* @param Range\RangeInterface<int> $range | ||
* | ||
* @dataProvider provideData | ||
*/ | ||
public function testRange(string $expected, string $string, Range\RangeInterface $range): void | ||
{ | ||
static::assertSame($expected, Grapheme\range($string, $range)); | ||
} | ||
|
||
/** | ||
* @return list<{0: string, 1: string, 2: Range\RangeInterface<int>}> | ||
*/ | ||
public function provideData(): array | ||
{ | ||
return [ | ||
['', '', Range\between(0, 5, upper_inclusive: true)], | ||
['Hello,', 'Hello, World!', Range\between(0, 5, upper_inclusive: true)], | ||
['Hello', 'Hello, World!', Range\between(0, 5, upper_inclusive: false)], | ||
['Hello, World!', 'Hello, World!', Range\from(0)], | ||
['World!', 'Hello, World!', Range\between(7, 12, upper_inclusive: true)], | ||
['World', 'Hello, World!', Range\between(7, 12, upper_inclusive: false)], | ||
['سيف', 'مرحبا سيف', Range\between(6, 9, upper_inclusive: true)], | ||
['اهلا', 'اهلا بكم', Range\between(0, 3, upper_inclusive: true)], | ||
['اهلا', 'اهلا بكم', Range\between(0, 4, upper_inclusive: false)], | ||
['destiny', 'People linked by destiny will always find each other.', Range\between(17, 23, upper_inclusive: true)], | ||
['destiny', 'People linked by destiny will always find each other.', Range\between(17, 24, upper_inclusive: false)], | ||
['lö ', 'héllö wôrld', Range\between(3, 5, upper_inclusive: true)], | ||
['lö ', 'héllö wôrld', Range\between(3, 6, upper_inclusive: false)], | ||
['lö wôrld', 'héllö wôrld', Range\from(3)], | ||
['héll', 'héllö wôrld', Range\to(3, inclusive: true)], | ||
['hél', 'héllö wôrld', Range\to(3, inclusive: false)], | ||
['', 'lö wôrld', Range\between(3, 3)], | ||
['', 'fôo', Range\between(3, 3)], | ||
['', 'fôo', Range\between(3, 12)], | ||
['fôo', 'fôo', Range\full()], | ||
['he̡̙̬͎̿́̐̅̕͢l͕̮͕͈̜͐̈́̇̕͠ļ͚͉̗̘̽͑̿͑̚o̼̰̼͕̞̍̄̎̿̊,̻̰̻̘́̎͒̋͘͟ ̧̬̝͈̬̿͌̿̑̕ẉ̣̟͉̮͆̊̃͐̈́ờ̢̫͎͖̹͊́͐r̨̮͓͓̣̅̋͐͐͆ḻ̩̦͚̯͑̌̓̅͒d͇̯͔̼͍͛̾͛͡͝', 'he̡̙̬͎̿́̐̅̕͢l͕̮͕͈̜͐̈́̇̕͠ļ͚͉̗̘̽͑̿͑̚o̼̰̼͕̞̍̄̎̿̊,̻̰̻̘́̎͒̋͘͟ ̧̬̝͈̬̿͌̿̑̕ẉ̣̟͉̮͆̊̃͐̈́ờ̢̫͎͖̹͊́͐r̨̮͓͓̣̅̋͐͐͆ḻ̩̦͚̯͑̌̓̅͒d͇̯͔̼͍͛̾͛͡͝', Range\between(0, 11, true)], | ||
]; | ||
} | ||
|
||
public function testRangeThrowsForOutOfBoundOffset(): void | ||
{ | ||
$this->expectException(Exception\OutOfBoundsException::class); | ||
|
||
Grapheme\range('Hello', Range\from(10)); | ||
} | ||
|
||
public function testRangeThrowsForNegativeOutOfBoundOffset(): void | ||
{ | ||
$this->expectException(Exception\OutOfBoundsException::class); | ||
|
||
Grapheme\range('Hello', Range\from(-6)); | ||
} | ||
} |
Oops, something went wrong.