Skip to content

Commit

Permalink
[Str] add grarpheme support
Browse files Browse the repository at this point in the history
  • Loading branch information
azjezz committed Nov 2, 2020
1 parent c5256f3 commit e1b45c2
Show file tree
Hide file tree
Showing 30 changed files with 994 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"ext-bcmath": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-sodium": "*"
"ext-sodium": "*",
"ext-intl": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.4",
Expand Down
14 changes: 14 additions & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,20 @@ final class Loader
'Psl\Hash\equals',
'Psl\Hash\Hmac\hash',
'Psl\Hash\Hmac\algorithms',
'Psl\Str\Grapheme\contains',
'Psl\Str\Grapheme\contains_ci',
'Psl\Str\Grapheme\ends_with',
'Psl\Str\Grapheme\ends_with_ci',
'Psl\Str\Grapheme\length',
'Psl\Str\Grapheme\search',
'Psl\Str\Grapheme\search_ci',
'Psl\Str\Grapheme\search_last',
'Psl\Str\Grapheme\search_last_ci',
'Psl\Str\Grapheme\slice',
'Psl\Str\Grapheme\starts_with',
'Psl\Str\Grapheme\starts_with_ci',
'Psl\Str\Grapheme\strip_prefix',
'Psl\Str\Grapheme\strip_suffix',
];

public const INTERFACES = [
Expand Down
30 changes: 30 additions & 0 deletions src/Psl/Str/Grapheme/contains.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the 'haystack' string contains the 'needle' string.
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string. If the offset is out-of-bounds, a ViolationException will be
* thrown.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function contains(string $haystack, string $needle, int $offset = 0): bool
{
if ('' === $needle) {
Psl\Internal\validate_offset($offset, length($haystack));

return true;
}

return null !== search($haystack, $needle, $offset);
}
30 changes: 30 additions & 0 deletions src/Psl/Str/Grapheme/contains_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the 'haystack' string contains the 'needle' string.
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string. If the offset is out-of-bounds, a ViolationException will be
* thrown.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function contains_ci(string $haystack, string $needle, int $offset = 0): bool
{
if ('' === $needle) {
Psl\Internal\validate_offset($offset, length($haystack));

return true;
}

return null !== search_ci($haystack, $needle, $offset);
}
36 changes: 36 additions & 0 deletions src/Psl/Str/Grapheme/ends_with.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the string ends with the given suffix.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If unable to convert $string to UTF-16,
* or split it into graphemes.
*/
function ends_with(string $string, string $suffix): bool
{
if ($suffix === $string) {
return true;
}

$suffix_length = length($suffix);
$total_length = length($string);
if ($suffix_length > $total_length) {
return false;
}

/** @psalm-suppress MissingThrowsDocblock */
$position = search_last($string, $suffix);
if (null === $position) {
return false;
}

return $position + $suffix_length === $total_length;
}
36 changes: 36 additions & 0 deletions src/Psl/Str/Grapheme/ends_with_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the string ends with the given suffix (case-insensitive).
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If unable to convert $string to UTF-16,
* or split it into graphemes.
*/
function ends_with_ci(string $string, string $suffix): bool
{
if ($suffix === $string) {
return true;
}

$suffix_length = length($suffix);
$total_length = length($string);
if ($suffix_length > $total_length) {
return false;
}

/** @psalm-suppress MissingThrowsDocblock */
$position = search_last_ci($string, $suffix);
if (null === $position) {
return false;
}

return $position + $suffix_length === $total_length;
}
27 changes: 27 additions & 0 deletions src/Psl/Str/Grapheme/length.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strlen;

/**
* Returns the length of the given string in grapheme units
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If unable to convert $string to UTF-16,
* or split it into graphemes.
*/
function length(string $string): int
{
$length = grapheme_strlen($string);

Psl\invariant(null !== $length, 'unable to convert $string to UTF-16');
Psl\invariant(false !== $length, 'unable to split $string into graphemes');

return $length;
}
34 changes: 34 additions & 0 deletions src/Psl/Str/Grapheme/search.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strpos;

/**
* Returns the first position of the 'needle' string in the 'haystack' string
* grapheme units, or null if it isn't found.
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function search(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$offset = Psl\Internal\validate_offset($offset, length($haystack));

return false === ($pos = grapheme_strpos($haystack, $needle, $offset)) ?
null :
$pos;
}
34 changes: 34 additions & 0 deletions src/Psl/Str/Grapheme/search_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_stripos;

/**
* Returns the first position of the 'needle' string in the 'haystack' string,
* in grapheme units, or null if it isn't found (case-insensitive).
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If $offset is out-of-bounds.
*/
function search_ci(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$offset = Psl\Internal\validate_offset($offset, length($haystack));

return false === ($pos = grapheme_stripos($haystack, $needle, $offset)) ?
null :
$pos;
}
35 changes: 35 additions & 0 deletions src/Psl/Str/Grapheme/search_last.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strrpos;

/**
* Returns the last position of the 'needle' string in the 'haystack' string,
* or null if it isn't found.
*
* An optional offset determines where in the haystack (from the beginning) the
* search begins. If the offset is negative, the search will begin that many
* characters from the end of the string and go backwards.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function search_last(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$haystack_length = length($haystack);
Psl\invariant($offset >= -$haystack_length && $offset <= $haystack_length, 'Offset is out-of-bounds.');

return false === ($pos = grapheme_strrpos($haystack, $needle, $offset)) ?
null :
$pos;
}
35 changes: 35 additions & 0 deletions src/Psl/Str/Grapheme/search_last_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strripos;

/**
* Returns the last position of the 'needle' string in the 'haystack' string,
* or null if it isn't found (case-insensitive).
*
* An optional offset determines where in the haystack (from the beginning) the
* search begins. If the offset is negative, the search will begin that many
* characters from the end of the string and go backwards.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds.
*/
function search_last_ci(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$haystack_length = length($haystack);
Psl\invariant($offset >= -$haystack_length && $offset <= $haystack_length, 'Offset is out-of-bounds.');

return false === ($pos = grapheme_strripos($haystack, $needle, $offset)) ?
null :
$pos;
}
36 changes: 36 additions & 0 deletions src/Psl/Str/Grapheme/slice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns a substring of length `$length` of the given string starting at the
* `$offset`.
*
* If no length is given, the slice will contain the rest of the
* string. If the length is zero, the empty string will be returned. If the
* offset is out-of-bounds, an InvariantViolationException will be thrown.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If a negative $length is given, or $offset is out-of-bounds.
*/
function slice(string $string, int $offset, ?int $length = null): string
{
Psl\invariant(null === $length || $length >= 0, 'Expected a non-negative length.');
$string_length = length($string);
$offset = Psl\Internal\validate_offset($offset, $string_length);

if (0 === $offset && (null === $length || $string_length <= $length)) {
return $string;
}

if (null === $length) {
return (string) grapheme_substr($string, $offset);
}

return (string) grapheme_substr($string, $offset, $length);
}
16 changes: 16 additions & 0 deletions src/Psl/Str/Grapheme/starts_with.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

/**
* Returns whether the string starts with the given prefix.
*
* @psalm-pure
*/
function starts_with(string $string, string $prefix): bool
{
/** @psalm-suppress MissingThrowsDocblock */
return 0 === search($string, $prefix);
}
Loading

0 comments on commit e1b45c2

Please sign in to comment.