diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b809683b3..e0e080ae3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,10 +24,10 @@ jobs: - run: composer install --no-progress --prefer-dist - run: vendor/bin/tester tests -s -C - if: failure() - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: output - path: tests/Utils/output + path: tests/**/output code_coverage: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b30e7c69e..000000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -language: php -php: - - 7.2 - - 7.3 - - 7.4 - - 8.0snapshot - -before_install: - # turn off XDebug - - phpenv config-rm xdebug.ini || return 0 - -install: - - travis_retry composer install --no-progress --prefer-dist - -script: - - vendor/bin/tester tests -s - -after_failure: - # Print *.actual content - - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done - -jobs: - include: - - name: Nette Code Checker - php: 7.4 - install: - - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress - script: - - php temp/code-checker/code-checker --strict-types -i tests/Utils/fixtures.reflection - - - - name: Nette Coding Standard - php: 8.0snapshot - install: - - travis_retry composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress --ignore-platform-reqs - script: - - php temp/coding-standard/ecs check - - - - stage: Static Analysis (informative) - php: 7.4 - script: - - composer phpstan -- --no-progress - - - - stage: Code Coverage - php: 7.4 - script: - - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src - after_script: - - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar - - php coveralls.phar --verbose --config tests/.coveralls.yml - - - allow_failures: - - stage: Static Analysis (informative) - - stage: Code Coverage - - -dist: xenial - -cache: - directories: - - $HOME/.composer/cache - -notifications: - email: false diff --git a/src/Utils/HtmlStringable.php b/src/HtmlStringable.php similarity index 100% rename from src/Utils/HtmlStringable.php rename to src/HtmlStringable.php diff --git a/src/Utils/SmartObject.php b/src/SmartObject.php similarity index 100% rename from src/Utils/SmartObject.php rename to src/SmartObject.php diff --git a/src/Utils/StaticClass.php b/src/StaticClass.php similarity index 100% rename from src/Utils/StaticClass.php rename to src/StaticClass.php diff --git a/src/Utils/Translator.php b/src/Translator.php similarity index 100% rename from src/Utils/Translator.php rename to src/Translator.php diff --git a/src/Utils/Arrays.php b/src/Utils/Arrays.php index ffbb7cb0e..07241108f 100644 --- a/src/Utils/Arrays.php +++ b/src/Utils/Arrays.php @@ -99,9 +99,39 @@ public static function searchKey(array $array, $key): ?int } + /** + * Tests an array for the presence of value. + * @param mixed $value + */ + public static function contains(array $array, $value): bool + { + return in_array($value, $array, true); + } + + + /** + * Returns the first item from the array or null if array is empty. + * @return mixed + */ + public static function first(array $array) + { + return count($array) ? reset($array) : null; + } + + + /** + * Returns the last item from the array or null if array is empty. + * @return mixed + */ + public static function last(array $array) + { + return count($array) ? end($array) : null; + } + + /** * Inserts the contents of the $inserted array into the $array immediately after the $key. - * If $key is null (or does not exist), it is inserted at the end. + * If $key is null (or does not exist), it is inserted at the beginning. * @param string|int|null $key */ public static function insertBefore(array &$array, $key, array $inserted): void @@ -115,7 +145,7 @@ public static function insertBefore(array &$array, $key, array $inserted): void /** * Inserts the contents of the $inserted array into the $array before the $key. - * If $key is null (or does not exist), it is inserted at the beginning. + * If $key is null (or does not exist), it is inserted at the end. * @param string|int|null $key */ public static function insertAfter(array &$array, $key, array $inserted): void @@ -280,7 +310,7 @@ public static function pick(array &$array, $key, $default = null) * Tests whether at least one element in the array passes the test implemented by the * provided callback with signature `function ($value, $key, array $array): bool`. */ - public static function some(array $array, callable $callback): bool + public static function some(iterable $array, callable $callback): bool { foreach ($array as $k => $v) { if ($callback($v, $k, $array)) { @@ -295,7 +325,7 @@ public static function some(array $array, callable $callback): bool * Tests whether all elements in the array pass the test implemented by the provided function, * which has the signature `function ($value, $key, array $array): bool`. */ - public static function every(array $array, callable $callback): bool + public static function every(iterable $array, callable $callback): bool { foreach ($array as $k => $v) { if (!$callback($v, $k, $array)) { @@ -310,7 +340,7 @@ public static function every(array $array, callable $callback): bool * Calls $callback on all elements in the array and returns the array of return values. * The callback has the signature `function ($value, $key, array $array): bool`. */ - public static function map(array $array, callable $callback): array + public static function map(iterable $array, callable $callback): array { $res = []; foreach ($array as $k => $v) { @@ -320,12 +350,40 @@ public static function map(array $array, callable $callback): array } + /** + * Invokes all callbacks and returns array of results. + * @param callable[] $callbacks + */ + public static function invoke(iterable $callbacks, ...$args): array + { + $res = []; + foreach ($callbacks as $k => $cb) { + $res[$k] = $cb(...$args); + } + return $res; + } + + + /** + * Invokes method on every object in an array and returns array of results. + * @param object[] $objects + */ + public static function invokeMethod(iterable $objects, string $method, ...$args): array + { + $res = []; + foreach ($objects as $k => $obj) { + $res[$k] = $obj->$method(...$args); + } + return $res; + } + + /** * Copies the elements of the $array array to the $object object and then returns it. * @param object $object * @return object */ - public static function toObject(array $array, $object) + public static function toObject(iterable $array, $object) { foreach ($array as $k => $v) { $object->$k = $v; diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php index abaa26a33..c7fb74d71 100644 --- a/src/Utils/Strings.php +++ b/src/Utils/Strings.php @@ -158,7 +158,7 @@ public static function toAscii(string $s): string $s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]); // transliteration (by Transliterator and iconv) is not optimal, replace some characters directly - $s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю + $s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß if ($iconv !== 'libiconv') { $s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔ } @@ -514,7 +514,7 @@ public static function matchAll(string $subject, string $pattern, int $flags = 0 * @param string|array $pattern * @param string|callable $replacement */ - public static function replace(string $subject, $pattern, $replacement = null, int $limit = -1): string + public static function replace(string $subject, $pattern, $replacement = '', int $limit = -1): string { if (is_object($replacement) || is_array($replacement)) { if (!is_callable($replacement, false, $textual)) { @@ -522,7 +522,7 @@ public static function replace(string $subject, $pattern, $replacement = null, i } return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]); - } elseif ($replacement === null && is_array($pattern)) { + } elseif (is_array($pattern) && is_string(key($pattern))) { $replacement = array_values($pattern); $pattern = array_keys($pattern); } diff --git a/src/Utils/exceptions.php b/src/Utils/exceptions.php index 08ffec748..c45e54306 100644 --- a/src/Utils/exceptions.php +++ b/src/Utils/exceptions.php @@ -7,107 +7,6 @@ declare(strict_types=1); -namespace Nette; - - -/** - * The exception that is thrown when the value of an argument is - * outside the allowable range of values as defined by the invoked method. - */ -class ArgumentOutOfRangeException extends \InvalidArgumentException -{ -} - - -/** - * The exception that is thrown when a method call is invalid for the object's - * current state, method has been invoked at an illegal or inappropriate time. - */ -class InvalidStateException extends \RuntimeException -{ -} - - -/** - * The exception that is thrown when a requested method or operation is not implemented. - */ -class NotImplementedException extends \LogicException -{ -} - - -/** - * The exception that is thrown when an invoked method is not supported. For scenarios where - * it is sometimes possible to perform the requested operation, see InvalidStateException. - */ -class NotSupportedException extends \LogicException -{ -} - - -/** - * The exception that is thrown when a requested method or operation is deprecated. - */ -class DeprecatedException extends NotSupportedException -{ -} - - -/** - * The exception that is thrown when accessing a class member (property or method) fails. - */ -class MemberAccessException extends \Error -{ -} - - -/** - * The exception that is thrown when an I/O error occurs. - */ -class IOException extends \RuntimeException -{ -} - - -/** - * The exception that is thrown when accessing a file that does not exist on disk. - */ -class FileNotFoundException extends IOException -{ -} - - -/** - * The exception that is thrown when part of a file or directory cannot be found. - */ -class DirectoryNotFoundException extends IOException -{ -} - - -/** - * The exception that is thrown when an argument does not match with the expected value. - */ -class InvalidArgumentException extends \InvalidArgumentException -{ -} - - -/** - * The exception that is thrown when an illegal index was requested. - */ -class OutOfRangeException extends \OutOfRangeException -{ -} - - -/** - * The exception that is thrown when a value (typically returned by function) does not match with the expected value. - */ -class UnexpectedValueException extends \UnexpectedValueException -{ -} - namespace Nette\Utils; diff --git a/src/exceptions.php b/src/exceptions.php new file mode 100644 index 000000000..bbda7ddc9 --- /dev/null +++ b/src/exceptions.php @@ -0,0 +1,109 @@ + 'a', 'y' => 'b']); + $log = []; + $res = Arrays::every( + $arr, + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return true; } + ); + Assert::true($res); + Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); +}); diff --git a/tests/Utils/Arrays.first().phpt b/tests/Utils/Arrays.first().phpt new file mode 100644 index 000000000..6aa4925a1 --- /dev/null +++ b/tests/Utils/Arrays.first().phpt @@ -0,0 +1,21 @@ + 'Test::fn2 a,b'], + Arrays::invoke($list, 'a', 'b') +); + +Assert::same( + ['Test::fn1 a,b', 'key' => 'Test::fn2 a,b'], + Arrays::invoke(new ArrayIterator($list), 'a', 'b') +); diff --git a/tests/Utils/Arrays.invokeMethod.phpt b/tests/Utils/Arrays.invokeMethod.phpt new file mode 100644 index 000000000..8e1eec51d --- /dev/null +++ b/tests/Utils/Arrays.invokeMethod.phpt @@ -0,0 +1,35 @@ + new Test2]; + +Assert::same( + ['Test1 a,b', 'key' => 'Test2 a,b'], + Arrays::invokeMethod($list, 'fn', 'a', 'b') +); + +Assert::same( + ['Test1 a,b', 'key' => 'Test2 a,b'], + Arrays::invokeMethod(new ArrayIterator($list), 'fn', 'a', 'b') +); diff --git a/tests/Utils/Arrays.last().phpt b/tests/Utils/Arrays.last().phpt new file mode 100644 index 000000000..58ed411c7 --- /dev/null +++ b/tests/Utils/Arrays.last().phpt @@ -0,0 +1,20 @@ + 'aa', 'y' => 'bb'], $res); Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); }); + +test('', function () { + $arr = new ArrayIterator(['x' => 'a', 'y' => 'b']); + $log = []; + $res = Arrays::map( + $arr, + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v . $v; } + ); + Assert::same(['x' => 'aa', 'y' => 'bb'], $res); + Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); +}); diff --git a/tests/Utils/Arrays.some().phpt b/tests/Utils/Arrays.some().phpt index 40eac1f2b..3cbfadb3a 100644 --- a/tests/Utils/Arrays.some().phpt +++ b/tests/Utils/Arrays.some().phpt @@ -78,3 +78,14 @@ test('', function () { Assert::true($res); Assert::same([['a', 'x', $arr]], $log); }); + +test('', function () { + $arr = new ArrayIterator(['x' => 'a', 'y' => 'b']); + $log = []; + $res = Arrays::some( + $arr, + function ($v, $k, $arr) use (&$log) { $log[] = func_get_args(); return $v === 'a'; } + ); + Assert::true($res); + Assert::same([['a', 'x', $arr]], $log); +}); diff --git a/tests/Utils/Arrays.toObject.phpt b/tests/Utils/Arrays.toObject.phpt index 5a2ce59d5..228fa02f0 100644 --- a/tests/Utils/Arrays.toObject.phpt +++ b/tests/Utils/Arrays.toObject.phpt @@ -28,3 +28,11 @@ test('', function () { Assert::type(stdClass::class, $res); Assert::same(['a' => 1, 'b' => 2], (array) $res); }); + +test('', function () { + $obj = new stdClass; + $res = Arrays::toObject(new ArrayIterator(['a' => 1, 'b' => 2]), $obj); + Assert::same($res, $obj); + Assert::type(stdClass::class, $res); + Assert::same(['a' => 1, 'b' => 2], (array) $res); +}); diff --git a/tests/Utils/Strings.replace().phpt b/tests/Utils/Strings.replace().phpt index 999d12ba9..c1b94b504 100644 --- a/tests/Utils/Strings.replace().phpt +++ b/tests/Utils/Strings.replace().phpt @@ -32,3 +32,5 @@ Assert::same('#@ @@@#d!', Strings::replace('hello world!', [ '#([e-l])+#' => '#', '#[o-w]#' => '@', ])); +Assert::same(' !', Strings::replace('hello world!', '#\w#')); +Assert::same(' !', Strings::replace('hello world!', ['#\w#'])); diff --git a/tests/Utils/Strings.toAscii().phpt b/tests/Utils/Strings.toAscii().phpt index 0f16fc6df..9bcc51d10 100644 --- a/tests/Utils/Strings.toAscii().phpt +++ b/tests/Utils/Strings.toAscii().phpt @@ -13,7 +13,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::same('ZLUTOUCKY KUN oooo--', Strings::toAscii("\u{17D}LU\u{164}OU\u{10C}K\u{DD} K\u{16E}\u{147} \u{F6}\u{151}\u{F4}o\x2d\u{2013}")); // ŽLUŤOUČKÝ KŮŇ öőôo +Assert::same('ZLUTOUCKY KUN oeooo--', Strings::toAscii("\u{17D}LU\u{164}OU\u{10C}K\u{DD} K\u{16E}\u{147} \u{F6}\u{151}\u{F4}o\x2d\u{2013}")); // ŽLUŤOUČKÝ KŮŇ öőôo Assert::same('Zlutoucky kun', Strings::toAscii("Z\u{30C}lut\u{30C}ouc\u{30C}ky\u{301} ku\u{30A}n\u{30C}")); // Žluťoučký kůň with combining characters Assert::same('Z `\'"^~?', Strings::toAscii("\u{17D} `'\"^~?")); Assert::same('"""\'\'\'>><<^', Strings::toAscii("\u{201E}\u{201C}\u{201D}\u{201A}\u{2018}\u{2019}\u{BB}\u{AB}\u{B0}")); // „“”‚‘’»«° @@ -31,3 +31,4 @@ if (class_exists('Transliterator') && \Transliterator::create('Any-Latin; Latin- Assert::same('Ya ya Yu yu', Strings::toAscii("\u{42F} \u{44F} \u{42E} \u{44E}")); // Я я Ю ю +Assert::same('Ae Oe Ue Ss ae oe ue ss', Strings::toAscii("\u{c4} \u{d6} \u{dc} \u{1e9e} \u{e4} \u{f6} \u{fc} \u{df}")); // Ä Ö Ü ẞ ä ö ü ß diff --git a/tests/Utils/Strings.webalize().phpt b/tests/Utils/Strings.webalize().phpt index a2ae1ed65..a85967beb 100644 --- a/tests/Utils/Strings.webalize().phpt +++ b/tests/Utils/Strings.webalize().phpt @@ -13,8 +13,8 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::same('zlutoucky-kun-oooo', Strings::webalize("&\u{17D}LU\u{164}OU\u{10C}K\u{DD} K\u{16E}\u{147} \u{F6}\u{151}\u{F4}o!")); // &ŽLUŤOUČKÝ KŮŇ öőôo! -Assert::same('ZLUTOUCKY-KUN-oooo', Strings::webalize("&\u{17D}LU\u{164}OU\u{10C}K\u{DD} K\u{16E}\u{147} \u{F6}\u{151}\u{F4}o!", null, false)); // &ŽLUŤOUČKÝ KŮŇ öőôo! +Assert::same('zlutoucky-kun-oeooo', Strings::webalize("&\u{17D}LU\u{164}OU\u{10C}K\u{DD} K\u{16E}\u{147} \u{F6}\u{151}\u{F4}o!")); // &ŽLUŤOUČKÝ KŮŇ öőôo! +Assert::same('ZLUTOUCKY-KUN-oeooo', Strings::webalize("&\u{17D}LU\u{164}OU\u{10C}K\u{DD} K\u{16E}\u{147} \u{F6}\u{151}\u{F4}o!", null, false)); // &ŽLUŤOUČKÝ KŮŇ öőôo! if (class_exists('Transliterator') && \Transliterator::create('Any-Latin; Latin-ASCII')) { Assert::same('1-4-!', Strings::webalize("\u{BC} !", '!')); } diff --git a/tests/php-win.ini b/tests/php-win.ini deleted file mode 100644 index e7bafb06e..000000000 --- a/tests/php-win.ini +++ /dev/null @@ -1,10 +0,0 @@ -[PHP] -extension_dir = "./ext" -extension=php_mbstring.dll -extension=php_gd2.dll -extension=php_fileinfo.dll -extension=php_openssl.dll -extension=php_intl.dll - -[Zend] -;zend_extension="./ext/php_xdebug-2.0.5-5.3-vc6.dll"