Removed AString and IString and turned WString into a utility class like XString for multibyte strings.

This commit is contained in:
flash 2024-01-04 02:07:43 +00:00
parent e31781c69f
commit 18397477d6
14 changed files with 285 additions and 1377 deletions

View File

@ -1 +1 @@
0.2311.201900
0.2401.40206

108
composer.lock generated
View File

@ -68,16 +68,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.17.1",
"version": "v4.18.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"shasum": ""
},
"require": {
@ -118,9 +118,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0"
},
"time": "2023-08-13T19:53:39+00:00"
"time": "2023-12-10T21:03:43+00:00"
},
{
"name": "phar-io/manifest",
@ -235,16 +235,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.41",
"version": "1.10.50",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "c6174523c2a69231df55bdc65b61655e72876d76"
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76",
"reference": "c6174523c2a69231df55bdc65b61655e72876d76",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4",
"reference": "06a98513ac72c03e8366b5a0cb00750b487032e4",
"shasum": ""
},
"require": {
@ -293,27 +293,27 @@
"type": "tidelift"
}
],
"time": "2023-11-05T12:57:57+00:00"
"time": "2023-12-13T10:59:42+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.7",
"version": "10.1.11",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "355324ca4980b8916c18b9db29f3ef484078f26e"
"reference": "78c3b7625965c2513ee96569a4dbb62601784145"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e",
"reference": "355324ca4980b8916c18b9db29f3ef484078f26e",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145",
"reference": "78c3b7625965c2513ee96569a4dbb62601784145",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.15",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1",
"phpunit/php-file-iterator": "^4.0",
"phpunit/php-text-template": "^3.0",
@ -363,7 +363,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.7"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11"
},
"funding": [
{
@ -371,7 +371,7 @@
"type": "github"
}
],
"time": "2023-10-04T15:34:17+00:00"
"time": "2023-12-21T15:38:30+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -618,16 +618,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.4.2",
"version": "10.5.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "cacd8b9dd224efa8eb28beb69004126c7ca1a1a1"
"reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/cacd8b9dd224efa8eb28beb69004126c7ca1a1a1",
"reference": "cacd8b9dd224efa8eb28beb69004126c7ca1a1a1",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ed21115d505b4b4f7dc7b5651464e19a2c7f7856",
"reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856",
"shasum": ""
},
"require": {
@ -667,7 +667,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "10.4-dev"
"dev-main": "10.5-dev"
}
},
"autoload": {
@ -699,7 +699,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.4.2"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.5"
},
"funding": [
{
@ -715,7 +715,7 @@
"type": "tidelift"
}
],
"time": "2023-10-26T07:21:45+00:00"
"time": "2023-12-27T15:13:52+00:00"
},
{
"name": "sebastian/cli-parser",
@ -963,20 +963,20 @@
},
{
"name": "sebastian/complexity",
"version": "3.1.0",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "68cfb347a44871f01e33ab0ef8215966432f6957"
"reference": "68ff824baeae169ec9f2137158ee529584553799"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68cfb347a44871f01e33ab0ef8215966432f6957",
"reference": "68cfb347a44871f01e33ab0ef8215966432f6957",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
"reference": "68ff824baeae169ec9f2137158ee529584553799",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.10",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@ -985,7 +985,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.1-dev"
"dev-main": "3.2-dev"
}
},
"autoload": {
@ -1009,7 +1009,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"security": "https://github.com/sebastianbergmann/complexity/security/policy",
"source": "https://github.com/sebastianbergmann/complexity/tree/3.1.0"
"source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0"
},
"funding": [
{
@ -1017,20 +1017,20 @@
"type": "github"
}
],
"time": "2023-09-28T11:50:59+00:00"
"time": "2023-12-21T08:37:17+00:00"
},
{
"name": "sebastian/diff",
"version": "5.0.3",
"version": "5.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b"
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
"reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
"shasum": ""
},
"require": {
@ -1043,7 +1043,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
"dev-main": "5.1-dev"
}
},
"autoload": {
@ -1076,7 +1076,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
"source": "https://github.com/sebastianbergmann/diff/tree/5.0.3"
"source": "https://github.com/sebastianbergmann/diff/tree/5.1.0"
},
"funding": [
{
@ -1084,7 +1084,7 @@
"type": "github"
}
],
"time": "2023-05-01T07:48:21+00:00"
"time": "2023-12-22T10:55:06+00:00"
},
{
"name": "sebastian/environment",
@ -1292,20 +1292,20 @@
},
{
"name": "sebastian/lines-of-code",
"version": "2.0.1",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d"
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"reference": "649e40d279e243d985aa8fb6e74dd5bb28dc185d",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.10",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
@ -1338,7 +1338,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.1"
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2"
},
"funding": [
{
@ -1346,7 +1346,7 @@
"type": "github"
}
],
"time": "2023-08-31T09:25:50+00:00"
"time": "2023-12-21T08:38:20+00:00"
},
{
"name": "sebastian/object-enumerator",
@ -1634,16 +1634,16 @@
},
{
"name": "theseer/tokenizer",
"version": "1.2.1",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"shasum": ""
},
"require": {
@ -1672,7 +1672,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.1"
"source": "https://github.com/theseer/tokenizer/tree/1.2.2"
},
"funding": [
{
@ -1680,7 +1680,7 @@
"type": "github"
}
],
"time": "2021-07-28T10:34:58+00:00"
"time": "2023-11-20T00:12:19+00:00"
}
],
"aliases": [],
@ -1693,5 +1693,5 @@
"ext-mbstring": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View File

@ -1,278 +0,0 @@
<?php
// AString.php
// Created: 2021-04-26
// Updated: 2023-11-09
namespace Index;
use Traversable;
use InvalidArgumentException;
/**
* Provides an immutable ASCII string with arrow methods.
*
* @deprecated IString, AString and WString are deprecated and will be removed. Use XString methods instead.
*/
final class AString implements IString {
use XStringTrait;
private string $value;
/**
* Create an AString instance.
*
* @param string $value PHP string to inherit.
* @return AString New instance of AString.
*/
public function __construct(string $value) {
$this->value = $value;
}
public function getLength(): int {
return strlen($this->value);
}
public function isEmpty(): bool {
return $this->value === '';
}
public function __toString(): string {
return $this->value;
}
/**
* Checks if an offset exists in the string.
*
* You should call isset($string[$offset]) instead of $string->offsetExists($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetexists.php
* @param int $offset Character offset.
* @return bool true if it exists, false if not.
*/
public function offsetExists(mixed $offset): bool {
return isset($this->value[$offset]);
}
/**
* Gets an offset from the string.
*
* You should do $string[$offset] instead of $string->offsetGet($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetget.php
* @param int $offset Character offset.
* @return string Character at that offset.
*/
public function offsetGet(mixed $offset): mixed {
return $this->value[$offset];
}
/**
* Gets an iterator object for this string.
*
* @return StringIterator An iterator for this string.
*/
public function getIterator(): Traversable {
return new StringIterator($this);
}
/**
* Returns the data which should be serialized as json.
*
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed Data to be passed to json_encode.
*/
public function jsonSerialize(): mixed {
return $this->value;
}
public function bencodeSerialise(): mixed {
return $this->value;
}
/**
* Gets a serialized representation of this object.
*
* @return array Serialized data.
*/
public function __serialize(): array {
return [$this->value];
}
/**
* Reconstructs an object from a serialized string.
*
* @param array $serialized Serialized data.
*/
public function __unserialize(array $serialized): void {
$this->value = $serialized[0];
}
/**
* Checks whether this string is identical to another.
*
* @param mixed $other An instance of AString or a PHP string.
* @return bool true if the strings have the same value, false if not.
*/
public function equals(mixed $other): bool {
return $this->compare($other) === 0;
}
/**
* Compares whether this string is identical to another.
*
* @param mixed $other An instance of IString or a PHP string.
*/
public function compare(mixed $other): int {
return strcmp($this->value, (string)$other);
}
/**
* Creates a new identical AString instance.
*
* This method is somewhat pointless, given the immutable nature of this object,
* but rather people calling this instead of calling ->substring(0);
*
* @return AString A new identical instance of AString.
*/
public function clone(): mixed {
return new AString($this->value);
}
public function indexOf(IString|string $text, int $offset = 0): int {
$pos = strpos($this->value, (string)$text, $offset);
if($pos === false)
return -1;
return $pos;
}
public function contains(IString|string $text): bool {
return str_contains($this->value, (string)$text);
}
public function substring(int $offset, int|null $length = null): IString {
return new AString(substr($this->value, $offset, $length));
}
public function replace(IString|string $search, IString|string $replace): IString {
return new AString(str_replace((string)$search, (string)$replace, $this->value));
}
public function append(IString|string $string): IString {
return new AString($this->value . (string)$string);
}
public function prepend(IString|string $string): IString {
return new AString(((string)$string) . $this->value);
}
public function split(IString|string $separator, int $limit = PHP_INT_MAX): array {
$separator = (string)$separator;
if(empty($separator))
throw new InvalidArgumentException('$separator may not be empty.');
return XArray::select(
explode($separator, $this->value, $limit),
fn($str) => new AString($str)
);
}
public function chunk(int $chunkSize): array {
return XArray::select(
str_split($this->value, $chunkSize),
fn($str) => new AString($str)
);
}
public function trim(IString|string $characters = IString::TRIM_CHARS): IString {
return new AString(trim($this->value, (string)$characters));
}
public function trimStart(IString|string $characters = IString::TRIM_CHARS): IString {
return new AString(ltrim($this->value, (string)$characters));
}
public function trimEnd(IString|string $characters = IString::TRIM_CHARS): IString {
return new AString(rtrim($this->value, (string)$characters));
}
public function toLower(): IString {
return new AString(strtolower($this->value));
}
public function toUpper(): IString {
return new AString(strtoupper($this->value));
}
public function reverse(): IString {
return new AString(strrev($this->value));
}
public function countUnique(): int {
return XString::countUnique($this->value);
}
public function startsWith(IString|string $text): bool {
return str_starts_with($this->value, (string)$text);
}
public function endsWith(IString|string $text): bool {
return str_ends_with($this->value, (string)$text);
}
/**
* Casts this AString to a WString.
*
* @param ?string $encoding Intended encoding, null for the Index-level default.
* @param bool $convert true to convert the string to the target encoding, false to leave the bytes as-is.
* @return WString A WString of the provided encoding with the value of this AString.
*/
public function toWString(?string $encoding = null, bool $convert = true): WString {
$value = $this->value;
$encoding ??= WString::getDefaultEncoding();
if($convert)
$value = mb_convert_encoding($value, $encoding, 'ascii');
return new WString($value, $encoding);
}
/**
* Joins an iterable object together with a separator to create a string.
*
* @param iterable $source Source object.
* @param IString|string $separator Separator to use as glue.
* @return AString Resulting string.
*/
public static function join(iterable $source, IString|string $separator = ''): AString {
if(!is_array($source)) {
$parts = [];
foreach($source as $value)
$parts[] = $value;
$source = $parts;
}
return new AString(implode((string)$separator, $source));
}
/**
* Returns a reusable empty string instance.
*
* @return AString An empty string.
*/
public static function empty(): AString {
static $empty = null;
$empty ??= new AString('');
return $empty;
}
/**
* Converts a value to AString.
*
* @param mixed $value Source value.
* @return AString An AString representing the given value.
*/
public static function cast(mixed $value): AString {
if($value instanceof AString)
return $value;
if($value instanceof WString)
return $value->toAString();
return new AString((string)$value);
}
}

View File

@ -1,7 +1,7 @@
<?php
// DateTime.php
// Created: 2021-06-11
// Updated: 2023-01-01
// Updated: 2024-01-04
namespace Index;
@ -344,15 +344,6 @@ class DateTime extends DateTimeImmutable implements JsonSerializable, Stringable
return $this->add(TimeSpan::fromMicroseconds($micros));
}
/**
* Alias for subtract, must exist because of the DateTimeImmutable inheritance but I don't like short names.
*
* @internal
*/
public function sub(DateInterval $interval): DateTime {
return $this->subtract($interval);
}
/**
* Subtracts a time period from this DateTime.
*

View File

@ -1,18 +0,0 @@
<?php
// ICloneable.php
// Created: 2021-04-26
// Updated: 2021-05-12
namespace Index;
/**
* Provides an interface for creating a clone of an object in a controlled manner.
*/
interface ICloneable {
/**
* Create a new object that is a copy of the current instance.
*
* @return mixed A copy of the current instance.
*/
function clone(): mixed;
}

View File

@ -1,198 +0,0 @@
<?php
// IString.php
// Created: 2021-06-20
// Updated: 2023-11-09
namespace Index;
use ArrayAccess;
use Countable;
use IteratorAggregate;
use JsonSerializable;
use Stringable;
use Index\Serialisation\IBencodeSerialisable;
/**
* @deprecated IString, AString and WString are deprecated and will be removed. Use XString methods instead.
*/
interface IString extends ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Stringable, IComparable, ICloneable, IEquatable, IBencodeSerialisable {
/**
* Default trim characters.
*
* @var string
*/
const TRIM_CHARS = " \n\r\t\v\0";
/**
* Gets the length of the string.
*
* @return int Length of the string.
*/
function getLength(): int;
/**
* Returns whether the string is empty.
*
* @return bool true if the string is empty, false if not.
*/
function isEmpty(): bool;
/**
* Returns the index of a substring.
*
* @see https://www.php.net/manual/en/function.strpos
* @param IString|string $text Substring to look for.
* @param int $offset Offset from which to start looking.
* @return int Offset at which the substring can be found, -1 if the substring can't be found.
*/
function indexOf(IString|string $text, int $offset = 0): int;
/**
* Checks whether the string contains a substring.
*
* @see https://www.php.net/manual/en/function.str-contains
* @param IString|string $text Substring to look for.
* @return bool true if it could be found, false if not.
*/
function contains(IString|string $text): bool;
/**
* Returns a ranged substring.
*
* @see https://www.php.net/manual/en/function.substr
* @param int $offset Offset from which to start.
* @param int|null $length Amount of character to copy, null for the remainder of the string.
* @return IString An instance of IString containing the substring.
*/
function substring(int $offset, int|null $length = null): IString;
/**
* Checks if a string starts with a given substring.
*
* @see https://www.php.net/manual/en/function.str-starts-with
* @param IString|string $text The substring to check for.
* @return bool true if the substring was found, false if not.
*/
function startsWith(IString|string $text): bool;
/**
* Checks if a string ends with a given substring.
*
* @see https://www.php.net/manual/en/function.str-ends-with
* @param IString|string $text The substring to check for.
* @return bool true if the substring was found, false if not.
*/
function endsWith(IString|string $text): bool;
/**
* Replace a substring with another substring.
*
* @see https://www.php.net/manual/en/function.str-replace
* @param IString|string $search Substring that should be replaced.
* @param IString|string $replace Replacement values for what was found with search.
* @return IString A new IString instance with the replaced values.
*/
function replace(IString|string $search, IString|string $replace): IString;
/**
* Concatenates two strings with this one in the first position.
*
* @param IString|string $string String that should be appended.
* @return IString A new IString instance with the combined value.
*/
function append(IString|string $string): IString;
/**
* Concatenates two strings with this one in the last position.
*
* @param IString|string $string String that should be prepended.
* @return IString A new IString instance with the combined value.
*/
function prepend(IString|string $string): IString;
/**
* Split a string by a separator string into an array.
*
* Calling split with an empty string will not work, use ->chunk(1) instead.
*
* @see https://www.php.net/manual/en/function.explode
* @param IString|string $separator The boundary string.
* @param int $limit Maximum number of elements expected in the array.
* @return array The resulting array of substrings.
*/
function split(IString|string $separator, int $limit = PHP_INT_MAX): array;
/**
* Splits a string in substring chunks of a given length.
*
* @see https://www.php.net/manual/en/function.str-split.php
* @param int $chunkSize Maximum length of a chunk.
* @return array The resulting array of substrings.
*/
function chunk(int $chunkSize): array;
/**
* Strip whitespace or other characters from the beginning and end of a string.
*
* @see https://www.php.net/manual/en/function.trim
* @param IString|string $characters Characters to strip.
* @return IString A new instance of IString with the characters removed.
*/
function trim(IString|string $characters = self::TRIM_CHARS): IString;
/**
* Strip whitespace or other characters from the beginning of a string.
*
* @see https://www.php.net/manual/en/function.ltrim.php
* @param IString|string $characters Characters to strip.
* @return IString A new instance of IString with the characters removed.
*/
function trimStart(IString|string $characters = self::TRIM_CHARS): IString;
/**
* Strip whitespace or other characters from the end of a string.
*
* @see https://www.php.net/manual/en/function.rtrim.php
* @param IString|string $characters Characters to strip.
* @return IString A new instance of IString with the characters removed.
*/
function trimEnd(IString|string $characters = self::TRIM_CHARS): IString;
/**
* Convert string to lowercase.
*
* @see https://www.php.net/manual/en/function.strtolower.php
* @return IString A new IString instance with all lowercase characters.
*/
function toLower(): IString;
/**
* Convert string to uppercase.
*
* @see https://www.php.net/manual/en/function.strtoupper.php
* @return IString A new IString instance with all uppercase characters.
*/
function toUpper(): IString;
/**
* Reverses a string.
*
* @see https://www.php.net/manual/en/function.strrev
* @return IString A new IString instance containing the reversed string.
*/
function reverse(): IString;
/**
* Counts unique characters in a string.
*
* @return int Unique character count.
*/
function countUnique(): int;
/**
* Returns a copy of the standard PHP string.
*
* @return string PHP string.
*/
function __toString(): string;
}

View File

@ -1,83 +0,0 @@
<?php
// StringIterator.php
// Created: 2022-02-02
// Updated: 2023-11-09
namespace Index;
use Iterator;
/**
* Provides an iterator for IString types.
*
* @deprecated Will be removed along with IString, AString and WString.
*/
class StringIterator implements Iterator {
private IString $value;
private int $length;
private int $index = 0;
private bool $wasValid = false;
/**
* Creates the iterator.
*
* @param IString $string String to iterate.
*/
public function __construct(IString $string) {
$this->value = $string;
$this->length = $string->getLength();
}
/**
* Returns the current character.
*
* @see https://www.php.net/manual/en/iterator.current.php
* @return mixed Current character.
*/
public function current(): mixed {
return $this->value[$this->index];
}
/**
* Returns the index of the current character.
*
* @see https://www.php.net/manual/en/iterator.key.php
* @return int Index of the current character.
*/
public function key(): mixed {
return $this->index;
}
/**
* Move forward to the next character.
*
* @see https://www.php.net/manual/en/iterator.next.php
*/
public function next(): void {
$next = $this->index + 1;
$this->wasValid = $next < $this->length;
if($this->wasValid)
$this->index = $next;
}
/**
* Rewind to the first character.
*
* @see https://www.php.net/manual/en/iterator.rewind.php
*/
public function rewind(): void {
$this->index = 0;
$this->wasValid = true;
}
/**
* Checks if the current index is valid.
*
* @see https://www.php.net/manual/en/iterator.rewind.php
* @return bool Whether the current index is valid.
*/
public function valid(): bool {
return $this->wasValid;
}
}

View File

@ -1,394 +1,175 @@
<?php
// WString.php
// Created: 2021-06-22
// Updated: 2023-11-09
// Updated: 2024-01-04
namespace Index;
use Traversable;
use InvalidArgumentException;
use ValueError;
use Stringable;
/**
* Provides an immutable multi-byte string with arrow methods.
*
* @deprecated IString, AString and WString are deprecated and will be removed. Use XString methods instead.
* Provides various helper methods for multibyte strings.
*/
final class WString implements IString {
use XStringTrait;
final class WString {
/**
* Default characters for ::trim, ::trimStart and ::trimEnd.
*
* @var string
*/
public const TRIM_CHARS = "\0\t\n\v\f\r \u{85}\u{a0}\u{1680}\u{180e}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200a}\u{200b}\u{200c}\u{200d}\u{2028}\u{2029}\u{202f}\u{205f}\u{2060}\u{3000}\u{feff}";
private const TRIM_CHARS_CHARSET = 'UTF-8';
public const DEFAULT_ENCODING = 'utf-8';
private const TRIM_START = 0x01;
private const TRIM_END = 0x02;
private const TRIM_ALL = self::TRIM_START | self::TRIM_END;
private static $defaultEncoding = self::DEFAULT_ENCODING;
private string $value;
private string $encoding;
public function __construct(string $value = '', ?string $encoding = null) {
$encoding ??= self::$defaultEncoding;
if(!mb_check_encoding($value, $encoding))
throw new InvalidArgumentException('$value is not a valid value for the selected encoding.');
$this->value = $value;
$this->encoding = mb_preferred_mime_name($encoding);
}
public function getLength(): int {
return mb_strlen($this->value, $this->encoding);
/**
* Checks if a multibyte string starts with a given substring.
*
* @param Stringable|string $haystack String to search in.
* @param Stringable|string $needle Sustring to search for in the haystack.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return bool true if haystack begins with needle, false otherwise.
*/
public static function startsWith(Stringable|string $haystack, Stringable|string $needle, ?string $encoding = null): bool {
return mb_strpos((string)$haystack, (string)$needle, encoding: $encoding) === 0;
}
/**
* Returns the amount of bytes the string contains.
* Checks if a multibyte string ends with a given substring.
*
* @return int Amount of raw bytes.
* @param Stringable|string $haystack String to search in.
* @param Stringable|string $needle Sustring to search for in the haystack.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return bool true if haystack ends with needle, false otherwise.
*/
public function getByteCount(): int {
return strlen($this->value);
public static function endsWith(Stringable|string $haystack, Stringable|string $needle, ?string $encoding = null): bool {
$haystack = (string)$haystack;
$haystackLength = mb_strlen($haystack, $encoding);
$needle = (string)$needle;
$needleLength = mb_strlen($needle, $encoding);
return mb_substr($haystack, -$needleLength, encoding: $encoding) === $needle;
}
public function isEmpty(): bool {
// an empty string is an empty string so this should be fine regardless of encoding
return $this->value === '';
}
private static function trimInternal(Stringable|string $string, string $chars, ?string $encoding, ?string $charsEncoding, int $flags): string {
$encoding = $encoding === null ? mb_internal_encoding() : mb_preferred_mime_name($encoding);
$charsEncoding = $charsEncoding === null ? self::TRIM_CHARS_CHARSET : mb_preferred_mime_name($charsEncoding);
public function indexOf(IString|string $text, int $offset = 0): int {
$text = (string)self::castEncoding($text, $this->encoding);
$pos = mb_strpos($this->value, $text, $offset, $this->encoding);
if($pos === false)
return -1;
return $pos;
}
// this fucks, i hate character sets
if($encoding !== $charsEncoding) {
$questionMarkCharsEnc = mb_convert_encoding('?', $charsEncoding, 'utf-8');
$questionMarkStrEnc = mb_convert_encoding('?', $encoding, 'utf-8');
$hasQuestionMark = mb_strpos($chars, $questionMarkCharsEnc, encoding: $charsEncoding) !== false;
$chars = mb_convert_encoding($chars, $encoding, $charsEncoding);
public function contains(IString|string $text): bool {
return str_contains($this->value, (string)$text);
}
if(!$hasQuestionMark) {
$charsSplit = mb_str_split($chars, encoding: $encoding);
$chars = [];
foreach($charsSplit as $char) {
if(in_array($char, $chars))
continue;
$chars[] = $char;
}
public function substring(int $offset, int|null $length = null): IString {
return new WString(mb_substr($this->value, $offset, $length, $this->encoding), $this->encoding);
}
$chars = implode($chars);
}
}
public function startsWith(IString|string $text): bool {
$text = self::castEncoding($text, $this->encoding);
return $this->substring(0, $text->getLength())->equals($text);
}
public function endsWith(IString|string $text): bool {
$text = self::castEncoding($text, $this->encoding);
return $this->substring(0 - $text->getLength())->equals($text);
}
public function replace(IString|string $search, IString|string $replace): IString {
$search = (string)self::castEncoding($search, $this->encoding);
$replace = (string)self::castEncoding($replace, $this->encoding);
$parts = self::doRegex(fn() => mb_split(preg_quote($search), $this->value));
$subject = implode($replace, $parts);
return new WString($subject, $this->encoding);
}
public function append(IString|string $string): IString {
$string = self::castEncoding($string, $this->encoding);
return new WString($this->value . $string->value, $this->encoding);
}
public function prepend(IString|string $string): IString {
$string = self::castEncoding($string, $this->encoding);
return new WString($string->value . $this->value, $this->encoding);
}
public function split(IString|string $separator, int $limit = PHP_INT_MAX): array {
return XArray::select(
self::doRegex(fn() => mb_split(preg_quote((string)$separator), $this->value, $limit)),
fn($str) => new WString($str, $this->encoding)
);
}
public function chunk(int $chunkSize): array {
return XArray::select(
mb_str_split($this->value, $chunkSize, $this->encoding),
fn($str) => new WString($str, $this->encoding)
);
}
private function trimInternal(IString|string $characters, int $flags): IString {
if($flags & 0x01) {
if($characters instanceof WString)
$characters = (string)$characters->convertEncoding($this->encoding);
else
$characters = mb_convert_encoding((string)$characters, $this->encoding, 'ascii');
} else
$characters = (string)$characters;
$string = (string)$string;
$split = mb_str_split($string, encoding: $encoding);
$length = mb_strlen($string, $encoding);
$start = 0;
$end = $this->getLength() - 1;
$end = $length - 1;
if($flags & 0x02)
for(; $start < $this->getLength(); ++$start)
if(mb_strpos($characters, $this[$start], 0, $this->encoding) === false)
if($flags & self::TRIM_START)
for(; $start < $length; ++$start)
if(mb_strpos($chars, $split[$start], encoding: $encoding) === false)
break;
if($flags & 0x04)
if($flags & self::TRIM_END)
for(; $end > 0; --$end)
if(mb_strpos($characters, $this[$end], 0, $this->encoding) === false)
if(mb_strpos($chars, $split[$end], encoding: $encoding) === false)
break;
return $this->substring($start, $end - $start + 1);
return mb_substr($string, $start, $end - $start + 1, $encoding);
}
public function trim(IString|string $characters = IString::TRIM_CHARS, bool $convertChars = true): IString {
$flags = 0x06;
if($convertChars) $flags |= 0x01;
return $this->trimInternal($characters, $flags);
/**
* Strip whitespace (or other characters) from the start and end of a multibyte string.
*
* @param Stringable|string $string Input string.
* @param string $chars Characters to strip. List all characters you want. .. operator from trim is not supported.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Trimmed string.
*/
public static function trim(Stringable|string $string, string $chars = self::TRIM_CHARS, ?string $encoding = null, ?string $charsEncoding = null): string {
return self::trimInternal($string, $chars, $encoding, $charsEncoding, self::TRIM_ALL);
}
public function trimStart(IString|string $characters = IString::TRIM_CHARS, bool $convertChars = true): IString {
$flags = 0x02;
if($convertChars) $flags |= 0x01;
return $this->trimInternal($characters, $flags);
/**
* Strip whitespace (or other characters) from the start of a multibyte string.
*
* @param Stringable|string $string Input string.
* @param string $chars Characters to strip. List all characters you want. .. operator from ltrim is not supported.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Trimmed string.
*/
public static function trimStart(Stringable|string $string, string $chars = self::TRIM_CHARS, ?string $encoding = null, ?string $charsEncoding = null): string {
return self::trimInternal($string, $chars, $encoding, $charsEncoding, self::TRIM_START);
}
public function trimEnd(IString|string $characters = IString::TRIM_CHARS, bool $convertChars = true): IString {
$flags = 0x04;
if($convertChars) $flags |= 0x01;
return $this->trimInternal($characters, $flags);
/**
* Strip whitespace (or other characters) from the end of a multibyte string.
*
* @param Stringable|string $string Input string.
* @param string $chars Characters to strip. List all characters you want. .. operator from rtrim is not supported.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Trimmed string.
*/
public static function trimEnd(Stringable|string $string, string $chars = self::TRIM_CHARS, ?string $encoding = null, ?string $charsEncoding = null): string {
return self::trimInternal($string, $chars, $encoding, $charsEncoding, self::TRIM_END);
}
public function toLower(): IString {
return new WString(mb_strtolower($this->value, $this->encoding), $this->encoding);
/**
* Reverses a multibyte string.
*
* @param Stringable|string $string String to reverse.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return string Reversed string.
*/
public static function reverse(Stringable|string $string, ?string $encoding = null): string {
return implode(array_reverse(mb_str_split((string)$string, encoding: $encoding)));
}
public function toUpper(): IString {
return new WString(mb_strtoupper($this->value, $this->encoding), $this->encoding);
}
public function reverse(): IString {
$chars = [];
foreach($this as $char)
$chars[] = $char;
return new WString(implode(array_reverse($chars)), $this->encoding);
}
public function countUnique(): int {
/**
* Counts unique characters in a string.
*
* @param Stringable|string $string String to count unique characters of.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return int Unique character count.
*/
public static function countUnique(Stringable|string $string, ?string $encoding = null): int {
$string = mb_str_split((string)$string, encoding: $encoding);
$chars = [];
foreach($this as $char)
foreach($string as $char)
if(!in_array($char, $chars, true))
$chars[] = $char;
return count($chars);
}
public function __toString(): string {
return $this->value;
}
public function toAString(bool $convert = true): AString {
$value = $this->value;
if($convert && self::sameEncoding('ascii', $this->encoding))
$value = mb_convert_encoding($value, 'ascii', $this->encoding);
return new AString($value);
}
public function getEncoding(): string {
return $this->encoding;
}
public function convertEncoding(string $encoding): WString {
if(self::sameEncoding($encoding, $this->encoding))
return $this;
$value = mb_convert_encoding($this->value, $encoding, $this->encoding);
return new WString($value, $encoding);
}
/**
* Checks if an offset exists in the string.
* Check if a multibyte string is null or whitespace.
*
* You should call isset($string[$offset]) instead of $string->offsetExists($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetexists.php
* @param int $offset Character offset.
* @return bool true if it exists, false if not.
* @param Stringable|string|null $string String to check for whitespace.
* @param ?string $encoding String character encoding. null for mb_internal_encoding value.
* @return bool true if the string is whitespace, false if not.
*/
public function offsetExists(mixed $offset): bool {
$offset = (int)$offset;
return $offset >= 0 && $offset < $this->getLength();
}
/**
* Gets an offset from the string.
*
* You should do $string[$offset] instead of $string->offsetGet($offset).
*
* @see https://www.php.net/manual/en/arrayaccess.offsetget.php
* @param int $offset Character offset.
* @return string Character at that offset.
*/
public function offsetGet(mixed $offset): mixed {
return mb_substr($this->value, (int)$offset, 1, $this->encoding);
}
/**
* Gets an iterator object for this string.
*
* @return StringIterator An iterator for this string.
*/
public function getIterator(): Traversable {
return new StringIterator($this);
}
/**
* Returns the data which should be serialized as json.
*
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed Data to be passed to json_encode.
*/
public function jsonSerialize(): mixed {
return $this->value;
}
public function bencodeSerialise(): mixed {
return $this->value;
}
/**
* Gets a serialized representation of this object.
*
* @return array Serialized data.
*/
public function __serialize(): array {
return [$this->encoding, $this->value];
}
/**
* Reconstructs an object from a serialized string.
*
* @param array $serialized Serialized data.
*/
public function __unserialize(array $serialized): void {
[$this->encoding, $this->value] = $serialized;
}
/**
* Checks whether this string is identical to another.
*
* @param mixed $other An instance of IString or a PHP string.
* @return bool true if the strings have the same value, false if not.
*/
public function equals(mixed $other): bool {
return $this->compare($other) === 0;
}
/**
* Compares whether this string is identical to another.
*
* @param mixed $other An instance of IString or a PHP string.
*/
public function compare(mixed $other): int {
$other = self::castEncoding($other, $this->encoding);
return strcmp($this->value, (string)$other);
}
/**
* Creates a new identical WString instance.
*
* This method is somewhat pointless, given the immutable nature of this object,
* but rather people calling this instead of calling ->substring(0);
*
* @return WString A new identical instance of WString.
*/
public function clone(): mixed {
return new WString($this->value, $this->encoding);
}
private function doRegex(callable $callback): mixed {
$encoding = self::getRegexEncoding();
self::setRegexEncoding($this->encoding);
$result = $callback();
self::setRegexEncoding($encoding);
return $result;
}
private static function sameEncoding(string $name1, string $name2): bool {
return mb_preferred_mime_name($name1) === mb_preferred_mime_name($name2);
}
/**
* Joins an iterable object together with a separator to create a string.
*
* @param iterable $source Source object.
* @param IString|string $separator Separator to use as glue.
* @param ?string $encoding Desired encoding, null for Index default.
* @return WString Resulting string.
*/
public static function join(iterable $source, IString|string $separator = '', ?string $encoding = null): WString {
// func probably doesn't work entirely as intended
$encoding ??= self::getDefaultEncoding();
$separator = self::castEncoding($separator, $encoding);
if(!is_array($source)) {
$parts = [];
foreach($source as $value)
$parts[] = $value;
$source = $parts;
}
return new WString(implode((string)$separator, $source), $encoding);
}
public static function getDefaultEncoding(): string {
return self::$defaultEncoding;
}
public static function setDefaultEncoding(string $encoding = ''): void {
if(empty($encoding))
self::$defaultEncoding = self::DEFAULT_ENCODING;
else {
try {
if(mb_encoding_aliases($encoding) !== false)
self::$defaultEncoding = $encoding;
} catch(ValueError $ex) {}
}