[Str] Add the ability to supply string encoding (#55)

This commit is contained in:
Saif Eddin G 2020-09-27 17:17:52 +02:00 committed by GitHub
parent c471b63706
commit 25480b41fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 335 additions and 125 deletions

View File

@ -101,6 +101,7 @@ final class Loader
'Psl\Internal\validate_offset',
'Psl\Internal\validate_offset_lower_bound',
'Psl\Internal\lazy_iterator',
'Psl\Internal\internal_encoding',
'Psl\Iter\all',
'Psl\Iter\any',
'Psl\Iter\apply',

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Psl\Internal;
use Psl;
use Psl\Type;
use Psl\Exception;
use function in_array;
use function mb_internal_encoding;
use function mb_list_encodings;
/**
* @psalm-pure
*
* @psalm-suppress ImpureFunctionCall
*
* @throws Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function internal_encoding(?string $encoding = null): string
{
Psl\invariant(null === $encoding || in_array($encoding, mb_list_encodings(), true), 'Invalid encoding.');
return $encoding ?? (Type\is_string($internal_encoding = mb_internal_encoding()) ? $internal_encoding : 'UTF-8');
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl\Exception;
/**
* Returns the string with the first character capitalized.
*
@ -25,13 +27,17 @@ namespace Psl\Str;
* => Str('1337)
*
* @psalm-pure
*
* @throws Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function capitalize(string $string): string
function capitalize(string $string, ?string $encoding = null): string
{
if ('' === $string) {
return '';
}
/** @psalm-suppress MissingThrowsDocblock - $offset is within-bounds */
return concat(uppercase(slice($string, 0, 1)), slice($string, 1, length($string)));
return concat(
uppercase(slice($string, 0, 1, $encoding), $encoding),
slice($string, 1, length($string, $encoding), $encoding)
);
}

View File

@ -4,6 +4,13 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl\Exception;
use Psl\Internal;
use function mb_convert_case;
use const MB_CASE_TITLE;
/**
* Returns the string with all words capitalized.
*
@ -22,8 +29,10 @@ namespace Psl\Str;
* => Str('مرحبا بكم')
*
* @psalm-pure
*
* @throws Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function capitalize_words(string $string): string
function capitalize_words(string $string, ?string $encoding = null): string
{
return \mb_convert_case($string, \MB_CASE_TITLE, encoding($string));
return mb_convert_case($string, MB_CASE_TITLE, Internal\internal_encoding($encoding));
}

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_chr;
/**
* Return a specific character.
@ -18,11 +21,13 @@ use Psl;
* => Str('ل')
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function chr(int $ascii): string
function chr(int $ascii, ?string $encoding = null): string
{
/** @var string|false $char */
$char = \mb_chr($ascii, 'UTF-8');
$char = mb_chr($ascii, Internal\internal_encoding($encoding));
/** @psalm-suppress MissingThrowsDocblock */
Psl\invariant(is_string($char), 'Unexpected Error.');

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
/**
* Returns an array containing the string split into chunks of the given size.
@ -34,8 +35,9 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the given $chunk_size is negative or above the limit ( 65535 ).
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function chunk(string $string, int $chunk_size = 1): array
function chunk(string $string, int $chunk_size = 1, ?string $encoding = null): array
{
Psl\invariant($chunk_size >= 1, 'Expected a non-negative chunk size.');
if ('' === $string) {
@ -45,5 +47,5 @@ function chunk(string $string, int $chunk_size = 1): array
Psl\invariant(65535 >= $chunk_size, 'Maximum chunk length must not exceed 65535.');
/** @psalm-var list<string> */
return mb_str_split($string, $chunk_size, encoding($string));
return mb_str_split($string, $chunk_size, Internal\internal_encoding($encoding));
}

View File

@ -37,14 +37,15 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function contains(string $haystack, string $needle, int $offset = 0): bool
function contains(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): bool
{
if ('' === $needle) {
Psl\Internal\validate_offset($offset, length($haystack));
Psl\Internal\validate_offset($offset, length($haystack, $encoding));
return true;
}
return null !== search($haystack, $needle, $offset);
return null !== search($haystack, $needle, $offset, $encoding);
}

View File

@ -37,14 +37,15 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function contains_ci(string $haystack, string $needle, int $offset = 0): bool
function contains_ci(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): bool
{
if ('' === $needle) {
Psl\Internal\validate_offset($offset, length($haystack));
Psl\Internal\validate_offset($offset, length($haystack, $encoding));
return true;
}
return null !== search_ci($haystack, $needle, $offset);
return null !== search_ci($haystack, $needle, $offset, $encoding);
}

View File

@ -4,10 +4,16 @@ declare(strict_types=1);
namespace Psl\Str;
use function mb_detect_encoding;
/**
* Detect the encoding of the giving string.
*
* @psalm-return null|string The string encoding or null if unable to detect encoding.
*
* @psalm-pure
*/
function encoding(string $str): string
function encoding(string $string): ?string
{
return \mb_detect_encoding($str, null, true) ?: 'UTF-8';
return mb_detect_encoding($string, null, true) ?: null;
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns whether the string ends with the given suffix.
*
@ -31,21 +33,22 @@ namespace Psl\Str;
* => Bool(false)
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function ends_with(string $string, string $suffix): bool
function ends_with(string $string, string $suffix, ?string $encoding = null): bool
{
if ($suffix === $string) {
return true;
}
$suffix_length = length($suffix);
$total_length = length($string);
$suffix_length = length($suffix, $encoding);
$total_length = length($string, $encoding);
if ($suffix_length > $total_length) {
return false;
}
/** @psalm-suppress MissingThrowsDocblock - we don't supply $offset */
$position = search_last($string, $suffix);
$position = search_last($string, $suffix, 0, $encoding);
if (null === $position) {
return false;
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns whether the string ends with the given suffix (case-insensitive).
*
@ -31,21 +33,22 @@ namespace Psl\Str;
* => Bool(false)
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function ends_with_ci(string $string, string $suffix): bool
function ends_with_ci(string $string, string $suffix, ?string $encoding = null): bool
{
if ($suffix === $string) {
return true;
}
$suffix_length = length($suffix);
$total_length = length($string);
$suffix_length = length($suffix, $encoding);
$total_length = length($string, $encoding);
if ($suffix_length > $total_length) {
return false;
}
/** @psalm-suppress MissingThrowsDocblock - we don't supply $offset */
$position = search_last_ci($string, $suffix);
$position = search_last_ci($string, $suffix, 0, $encoding);
if (null === $position) {
return false;
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
/**
@ -15,12 +16,14 @@ use Psl\Internal;
* => Str('ss')
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function fold(string $str): string
function fold(string $str, ?string $encoding = null): string
{
foreach (Internal\CASE_FOLD as $k => $v) {
$str = replace($str, $k, $v);
$str = replace($str, $k, $v, $encoding);
}
return lowercase($str);
return lowercase($str, $encoding);
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Psl\Str;
use function vsprintf;
/**
* Return a formatted string.
*
@ -35,5 +37,5 @@ namespace Psl\Str;
*/
function format(string $format, ...$args): string
{
return \vsprintf($format, $args);
return vsprintf($format, $args);
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Psl\Str;
use function number_format;
/**
* Returns a string representation of the given number with grouped thousands.
*
@ -19,5 +21,5 @@ function format_number(
string $decimal_point = '.',
string $thousands_separator = ','
): string {
return \number_format($number, $decimals, $decimal_point, $thousands_separator);
return number_format($number, $decimals, $decimal_point, $thousands_separator);
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Psl\Str;
use function implode;
/**
* Join array elements with a string.
*
@ -24,5 +26,5 @@ namespace Psl\Str;
*/
function join(array $pieces, string $glue): string
{
return \implode($glue, $pieces);
return implode($glue, $pieces);
}

View File

@ -4,6 +4,11 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strlen;
/**
* Returns the length of the given string, i.e. the number of bytes.
*
@ -19,8 +24,10 @@ namespace Psl\Str;
* => Int(4)
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function length(string $str): int
function length(string $str, ?string $encoding = null): int
{
return \mb_strlen($str, encoding($str));
return mb_strlen($str, Internal\internal_encoding($encoding));
}

View File

@ -4,6 +4,11 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strtolower;
/**
* Returns the string with all alphabetic characters converted to lowercase.
*
@ -20,12 +25,14 @@ namespace Psl\Str;
* Str\lowercase('1337')
* => Str('1337')
*
* Str\contains('سيف')
* Str\lowercase('سيف')
* => Str('سيف')
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function lowercase(string $lowercase): string
function lowercase(string $lowercase, ?string $encoding = null): string
{
return \mb_strtolower($lowercase, encoding($lowercase));
return mb_strtolower($lowercase, Internal\internal_encoding($encoding));
}

View File

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_ord;
/**
@ -18,8 +21,10 @@ use function mb_ord;
* => Int(1604)
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function ord(string $char): int
function ord(string $char, ?string $encoding = null): int
{
return mb_ord($char, encoding($char));
return mb_ord($char, Internal\internal_encoding($encoding));
}

View File

@ -31,16 +31,17 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $pad_string is empty, or a negative $total_length is given.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function pad_left(string $string, int $total_length, string $pad_string = ' '): string
function pad_left(string $string, int $total_length, string $pad_string = ' ', ?string $encoding = null): string
{
Psl\invariant('' !== $pad_string, 'Expected a non-empty pad string.');
Psl\invariant($total_length >= 0, 'Expected a non-negative total length.');
while (length($string) < $total_length) {
$remaining = $total_length - length($string);
if ($remaining <= length($pad_string)) {
$pad_string = slice($pad_string, 0, $remaining);
while (length($string, $encoding) < $total_length) {
$remaining = $total_length - length($string, $encoding);
if ($remaining <= length($pad_string, $encoding)) {
$pad_string = slice($pad_string, 0, $remaining, $encoding);
}
$string = $pad_string . $string;

View File

@ -31,16 +31,17 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $pad_string is empty, or a negative $total_length is given.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function pad_right(string $string, int $total_length, string $pad_string = ' '): string
function pad_right(string $string, int $total_length, string $pad_string = ' ', ?string $encoding = null): string
{
Psl\invariant('' !== $pad_string, 'Expected a non-empty pad string.');
Psl\invariant($total_length >= 0, 'Expected a non-negative total length.');
while (length($string) < $total_length) {
$remaining = $total_length - length($string);
if ($remaining <= length($pad_string)) {
$pad_string = slice($pad_string, 0, $remaining);
while (length($string, $encoding) < $total_length) {
$remaining = $total_length - length($string, $encoding);
if ($remaining <= length($pad_string, $encoding)) {
$pad_string = slice($pad_string, 0, $remaining, $encoding);
}
$string .= $pad_string;

View File

@ -6,6 +6,8 @@ namespace Psl\Str;
use Psl;
use function str_repeat;
/**
* Returns the input string repeated `$multiplier` times.
*
@ -27,5 +29,5 @@ function repeat(string $string, int $multiplier): string
{
Psl\invariant($multiplier >= 0, 'Expected a non-negative multiplier');
return \str_repeat($string, $multiplier);
return str_repeat($string, $multiplier);
}

View File

@ -4,15 +4,19 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns the 'haystack' string with all occurrences of `$needle` replaced by
* `$replacement`.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function replace(string $haystack, string $needle, string $replacement): string
function replace(string $haystack, string $needle, string $replacement, ?string $encoding = null): string
{
if ('' === $needle || null === search($haystack, $needle)) {
if ('' === $needle || null === search($haystack, $needle, 0, $encoding)) {
return $haystack;
}

View File

@ -4,17 +4,24 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use function preg_quote;
use function preg_split;
/**
* Returns the 'haystack' string with all occurrences of `$needle` replaced by
* Returns the '$haystack' string with all occurrences of `$needle` replaced by
* `$replacement` (case-insensitive).
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function replace_ci(string $haystack, string $needle, string $replacement): string
function replace_ci(string $haystack, string $needle, string $replacement, ?string $encoding = null): string
{
if ('' === $needle || null === search_ci($haystack, $needle)) {
if ('' === $needle || null === search_ci($haystack, $needle, 0, $encoding)) {
return $haystack;
}
return implode($replacement, preg_split('{' . preg_quote($needle, '/') . '}iu', $haystack));
return join(preg_split('{' . preg_quote($needle, '/') . '}iu', $haystack), $replacement);
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns the '$haystack' string with all occurrences of the keys of
* `$replacements` replaced by the corresponding values.
@ -11,11 +13,13 @@ namespace Psl\Str;
* @psalm-param array<string, string> $replacements
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function replace_every(string $haystack, array $replacements): string
function replace_every(string $haystack, array $replacements, ?string $encoding = null): string
{
foreach ($replacements as $needle => $replacement) {
$haystack = replace($haystack, $needle, $replacement);
$haystack = replace($haystack, $needle, $replacement, $encoding);
}
return $haystack;

View File

@ -13,15 +13,17 @@ use Psl;
* @psalm-param array<string, string> $replacements
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function replace_every_ci(string $haystack, array $replacements): string
function replace_every_ci(string $haystack, array $replacements, ?string $encoding = null): string
{
foreach ($replacements as $needle => $replacement) {
if ('' === $needle || null === search_ci($haystack, $needle)) {
if ('' === $needle || null === search_ci($haystack, $needle, 0, $encoding)) {
continue;
}
$haystack = replace_ci($haystack, $needle, $replacement);
$haystack = replace_ci($haystack, $needle, $replacement, $encoding);
}
return $haystack;

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strpos;
/**
* Returns the first position of the 'needle' string in the 'haystack' string,
@ -17,14 +20,17 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function search(string $haystack, string $needle, int $offset = 0): ?int
function search(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): ?int
{
if ('' === $needle) {
return null;
}
$offset = Psl\Internal\validate_offset($offset, length($haystack));
$offset = Psl\Internal\validate_offset($offset, length($haystack, $encoding));
return false === ($pos = \mb_strpos($haystack, $needle, $offset, encoding($haystack))) ? null : $pos;
return false === ($pos = mb_strpos($haystack, $needle, $offset, Internal\internal_encoding($encoding))) ?
null :
$pos;
}

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_stripos;
/**
* Returns the first position of the 'needle' string in the 'haystack' string,
@ -17,14 +20,17 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If $offset is out-of-bounds.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function search_ci(string $haystack, string $needle, int $offset = 0): ?int
function search_ci(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): ?int
{
if ('' === $needle) {
return null;
}
$offset = Psl\Internal\validate_offset($offset, length($haystack));
$offset = Psl\Internal\validate_offset($offset, length($haystack, $encoding));
return false === ($pos = \mb_stripos($haystack, $needle, $offset, encoding($haystack))) ? null : $pos;
return false === ($pos = mb_stripos($haystack, $needle, $offset, Internal\internal_encoding($encoding))) ?
null :
$pos;
}

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strrpos;
/**
* Returns the last position of the 'needle' string in the 'haystack' string,
@ -17,15 +20,18 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function search_last(string $haystack, string $needle, int $offset = 0): ?int
function search_last(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): ?int
{
if ('' === $needle) {
return null;
}
$haystack_length = length($haystack);
$haystack_length = length($haystack, $encoding);
Psl\invariant($offset >= -$haystack_length && $offset <= $haystack_length, 'Offset is out-of-bounds.');
return false === ($pos = \mb_strrpos($haystack, $needle, $offset)) ? null : $pos;
return false === ($pos = mb_strrpos($haystack, $needle, $offset, Internal\internal_encoding($encoding))) ?
null :
$pos;
}

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strripos;
/**
* Returns the last position of the 'needle' string in the 'haystack' string,
@ -17,15 +20,18 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function search_last_ci(string $haystack, string $needle, int $offset = 0): ?int
function search_last_ci(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): ?int
{
if ('' === $needle) {
return null;
}
$haystack_length = length($haystack);
$haystack_length = length($haystack, $encoding);
Psl\invariant($offset >= -$haystack_length && $offset <= $haystack_length, 'Offset is out-of-bounds.');
return false === ($pos = \mb_strripos($haystack, $needle, $offset)) ? null : $pos;
return false === ($pos = mb_strripos($haystack, $needle, $offset, Internal\internal_encoding($encoding))) ?
null :
$pos;
}

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_substr;
/**
* Returns a substring of length `$length` of the given string starting at the
@ -17,16 +20,17 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If a negative $length is given.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function slice(string $string, int $offset, ?int $length = null): string
function slice(string $string, int $offset, ?int $length = null, ?string $encoding = null): string
{
Psl\invariant(null === $length || $length >= 0, 'Expected a non-negative length.');
$string_length = length($string);
$string_length = length($string, $encoding);
$offset = Psl\Internal\validate_offset($offset, $string_length);
if (0 === $offset && (null === $length || $string_length <= $length)) {
return $string;
}
return false === ($result = \mb_substr($string, $offset, $length, encoding($string))) ? '' : $result;
return (string) mb_substr($string, $offset, $length, Internal\internal_encoding($encoding));
}

View File

@ -17,16 +17,22 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If a negative $length is given.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function splice(string $string, string $replacement, int $offset = 0, ?int $length = null): string
{
function splice(
string $string,
string $replacement,
int $offset = 0,
?int $length = null,
?string $encoding = null
): string {
Psl\invariant(null === $length || $length >= 0, 'Expected a non-negative length.');
$total_length = length($string);
$total_length = length($string, $encoding);
$offset = Psl\Internal\validate_offset($offset, $total_length);
if (null === $length || ($offset + $length) >= $total_length) {
return slice($string, 0, $offset) . $replacement;
return slice($string, 0, $offset, $encoding) . $replacement;
}
return slice($string, 0, $offset) . $replacement . slice($string, $offset + $length);
return slice($string, 0, $offset, $encoding) . $replacement . slice($string, $offset + $length, null, $encoding);
}

View File

@ -19,21 +19,22 @@ use Psl\Math;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If a negative $limit is given.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function split(string $string, string $delimiter, ?int $limit = null): array
function split(string $string, string $delimiter, ?int $limit = null, ?string $encoding = null): array
{
Psl\invariant(null === $limit || $limit >= 1, 'Expected a non-negative limit');
if ('' === $delimiter) {
if (null === $limit || $limit >= length($string)) {
return chunk($string);
if (null === $limit || $limit >= length($string, $encoding)) {
return chunk($string, 1, $encoding);
}
if (1 === $limit) {
return [$string];
}
$result = chunk(slice($string, 0, $limit - 1));
$result[] = slice($string, $limit - 1);
$result = chunk(slice($string, 0, $limit - 1, $encoding), 1, $encoding);
$result[] = slice($string, $limit - 1, null, $encoding);
return $result;
}
@ -43,14 +44,14 @@ function split(string $string, string $delimiter, ?int $limit = null): array
$tail = $string;
$chunks = [];
$position = search($tail, $delimiter);
$position = search($tail, $delimiter, 0, $encoding);
while (1 < $limit && null !== $position) {
$result = slice($tail, 0, $position);
$result = slice($tail, 0, $position, $encoding);
$chunks[] = $result;
$tail = slice($tail, length($result) + length($delimiter));
$tail = slice($tail, length($result, $encoding) + length($delimiter, $encoding), null, $encoding);
$limit--;
$position = search($tail, $delimiter);
$position = search($tail, $delimiter, 0, $encoding);
}
$chunks[] = $tail;

View File

@ -4,13 +4,16 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns whether the string starts with the given prefix.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function starts_with(string $str, string $prefix): bool
function starts_with(string $str, string $prefix, ?string $encoding = null): bool
{
/** @psalm-suppress MissingThrowsDocblock - we don't supply $offset */
return 0 === search($str, $prefix);
return 0 === search($str, $prefix, 0, $encoding);
}

View File

@ -4,13 +4,16 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns whether the string starts with the given prefix (case-insensitive).
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function starts_with_ci(string $str, string $prefix): bool
function starts_with_ci(string $str, string $prefix, ?string $encoding = null): bool
{
/** @psalm-suppress MissingThrowsDocblock - we don't supply $offset */
return 0 === search_ci($str, $prefix);
return 0 === search_ci($str, $prefix, 0, $encoding);
}

View File

@ -4,18 +4,21 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns the string with the given prefix removed, or the string itself if
* it doesn't start with the prefix.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function strip_prefix(string $string, string $prefix): string
function strip_prefix(string $string, string $prefix, ?string $encoding = null): string
{
if ('' === $prefix || !starts_with($string, $prefix)) {
if ('' === $prefix || !starts_with($string, $prefix, $encoding)) {
return $string;
}
/** @psalm-suppress MissingThrowsDocblock - we don't supply $offset */
return slice($string, length($prefix));
return slice($string, length($prefix, $encoding), null, $encoding);
}

View File

@ -4,18 +4,21 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
/**
* Returns the string with the given suffix removed, or the string itself if
* it doesn't end with the suffix.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function strip_suffix(string $string, string $suffix): string
function strip_suffix(string $string, string $suffix, ?string $encoding = null): string
{
if ('' === $suffix || !ends_with($string, $suffix)) {
if ('' === $suffix || !ends_with($string, $suffix, $encoding)) {
return $string;
}
/** @psalm-suppress MissingThrowsDocblock - we are sure that the $offset is positive */
return slice($string, 0, length($string) - length($suffix));
return slice($string, 0, length($string, $encoding) - length($suffix, $encoding), $encoding);
}

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strimwidth;
/**
* Get truncated string with specified width.
@ -21,10 +24,11 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function truncate(string $str, int $offset, int $width, ?string $trim_marker = null): string
function truncate(string $str, int $offset, int $width, ?string $trim_marker = null, ?string $encoding = null): string
{
$offset = Psl\Internal\validate_offset($offset, length($str));
$offset = Internal\validate_offset($offset, length($str, $encoding));
return mb_strimwidth($str, $offset, $width, $trim_marker ?? '', encoding($str));
return mb_strimwidth($str, $offset, $width, $trim_marker ?? '', Internal\internal_encoding($encoding));
}

View File

@ -4,12 +4,19 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strtoupper;
/**
* Returns the string with all alphabetic characters converted to uppercase.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function uppercase(string $string): string
function uppercase(string $string, ?string $encoding = null): string
{
return \mb_strtoupper($string, encoding($string));
return mb_strtoupper($string, Internal\internal_encoding($encoding));
}

View File

@ -4,12 +4,19 @@ declare(strict_types=1);
namespace Psl\Str;
use Psl;
use Psl\Internal;
use function mb_strwidth;
/**
* Return width of length.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function width(string $str): int
function width(string $str, ?string $encoding = null): int
{
return \mb_strwidth($str, encoding($str));
return mb_strwidth($str, Internal\internal_encoding($encoding));
}

View File

@ -19,31 +19,35 @@ use Psl;
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If $break is empty, or $width is 0 and $cut is set to true.
* @throws Psl\Exception\InvariantViolationException If an invalid $encoding is provided.
*/
function wrap(string $string, int $width = 75, string $break = "\n", bool $cut = false): string
{
function wrap(
string $string,
int $width = 75,
string $break = "\n",
bool $cut = false,
?string $encoding = null
): string {
if ('' === $string) {
return '';
}
// if ('' === $break) -> throw
Psl\invariant('' !== $break, 'Break string cannot be empty.');
// if (0 === $width && $cut) -> throw
Psl\invariant(0 !== $width || !$cut, 'Cannot force cut when width is zero.');
$stringLength = length($string);
$breakLength = length($break);
$stringLength = length($string, $encoding);
$breakLength = length($break, $encoding);
$result = '';
$lastStart = $lastSpace = 0;
for ($current = 0; $current < $stringLength; ++$current) {
$char = slice($string, $current, 1);
$char = slice($string, $current, 1, $encoding);
$possibleBreak = $char;
if (1 !== $breakLength) {
$possibleBreak = slice($string, $current, $breakLength);
$possibleBreak = slice($string, $current, $breakLength, $encoding);
}
if ($possibleBreak === $break) {
$result .= slice($string, $lastStart, $current - $lastStart + $breakLength);
$result .= slice($string, $lastStart, $current - $lastStart + $breakLength, $encoding);
$current += $breakLength - 1;
$lastStart = $lastSpace = $current + 1;
continue;
@ -51,7 +55,7 @@ function wrap(string $string, int $width = 75, string $break = "\n", bool $cut =
if (' ' === $char) {
if ($current - $lastStart >= $width) {
$result .= slice($string, $lastStart, $current - $lastStart) . $break;
$result .= slice($string, $lastStart, $current - $lastStart, $encoding) . $break;
$lastStart = $current + 1;
}
$lastSpace = $current;
@ -59,20 +63,20 @@ function wrap(string $string, int $width = 75, string $break = "\n", bool $cut =
}
if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) {
$result .= slice($string, $lastStart, $current - $lastStart) . $break;
$result .= slice($string, $lastStart, $current - $lastStart, $encoding) . $break;
$lastStart = $lastSpace = $current;
continue;
}
if ($current - $lastStart >= $width && $lastStart < $lastSpace) {
$result .= slice($string, $lastStart, $lastSpace - $lastStart) . $break;
$result .= slice($string, $lastStart, $lastSpace - $lastStart, $encoding) . $break;
$lastStart = ++$lastSpace;
continue;
}
}
if ($lastStart !== $current) {
$result .= slice($string, $lastStart, $current - $lastStart);
$result .= slice($string, $lastStart, $current - $lastStart, $encoding);
}
return $result;

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Str;
use PHPUnit\Framework\TestCase;
use Psl\Str;
class EncodingTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testEncoding(?string $expected, string $string): void
{
self::assertSame($expected, Str\encoding($string));
}
public function provideData(): array
{
return [
['ASCII', 'hello'],
['UTF-8', 'سيف'],
['UTF-8', '🐘'],
[null, Str\Byte\chr(128)]
];
}
}