Commit ec831fed authored by Sergey Shadrin's avatar Sergey Shadrin

[#124524] Локализация

-added drush_language module
parent c852b6ca
This diff is collapsed.
# Drush 9 language commands
## Upgrading
At the time of this commit (Drush 9.0.0-beta5), Drush 9 needs commands using
Drupal services to be placed in a module, which was not necessary for Drush 8.
Quoting Greg Anderson in https://github.com/drush-ops/drush/issues/3050 :
<blockquote>Drush extensions that are not part of a module can be policy files
(hooks) or standalone commands, but cannot do dependency injection.
</blockquote>
As a result this plugin is no longer of type `drupal-drush` but is a normal
Drupal module. Assuming it is being used in a Composer `drupal-project`
workflow, this means:
* Removing the `drush/drush_language/` or `drush/contrib/drush_language`
directory.
* Running `composer update` to obtain the new version of the plugin implemented
as a module and placed in the `web/modules/contrib/drush_language` directory.
* Enabling the `drush_language` module to make the commands discoverable by
Drush.
{
"name": "drupal/drush_language",
"description": "Provides Drush commands for language manipulation",
"type": "drupal-module",
"license": "GPL-2.0+",
"minimum-stability": "dev",
"require": {
"webmozart/path-util": "^2.1.0"
},
"extra": {
"drush": {
"services": {
"drush.services.yml": "^9"
}
}
}
}
<?php
/**
* @file
* Default Settings.
*
* The directory relative to drupal root where custom settings can be exported
* and re-imported for deployment. Do not use a trailing slash.
*
* $settings['custom_translations_directory'] = '../custom-translations';
*/
services:
drush_language.commands:
class: \Drupal\drush_language\Commands\DrushLanguageCommands
arguments:
- '@drush_language.cli'
tags:
- { name: drush.command }
<?php
/**
* @file
* Drush (< 9) integration for the drush_language module.
*/
use Drupal\drush_language\Drush8Io;
/**
* Implements hook_drush_command().
*
* @See drush_parse_command() for a list of recognized keys.
*/
function drush_language_drush_command() {
$items = [];
$items['language-add'] = [
'description' => 'Add and import a new language definition',
'arguments' => [
'langcodes' => 'The langcodes of the languages for which a definition will be added.',
],
'aliases' => ['langadd'],
];
$items['language-default'] = [
'description' => 'Assign an enabled language as default',
'arguments' => [
'langcode' => 'The langcode of the language which will be set as the default language.',
],
'aliases' => ['langdef'],
];
$items['language-import-translations'] = [
'description' => 'Import a single .po file',
'arguments' => [
'.po file(s)' => 'Path to one or more .po files to import. If omitted, $settings[\'custom_translations_directory\'] must be set and all .po files from that directory will be taken. If langcode (overridable), project and version can be deduced from the filename, they will be stored in the translation database.',
],
'examples' => [
'Import all custom translations from the directory defined in $settings[\'custom_translations_directory\'].' => 'drush langimp',
'Import single file with explicit langcode' => 'drush langimp --langcode=ru file.po',
'Import not-customized (e.g. module) translations, without replacing custom translations, with auto langcode (these are the recognized patterns)' => 'drush langimp --langcode=eo --no-set-customized --no-replace-customized de.po foomodule.fr.po barmodule-8.x-2.2-rc1.es.po',
],
'options' => [
'langcode' => [
'description' => 'Language code to be imported. If not given, extracted from file name.',
'value' => 'optional',
],
'replace-customized' => [
'description' => 'Replace existing customized translations. Defaults to true.',
'value' => 'optional',
],
'replace-not-customized' => [
'description' => 'Replace existing not-customized translations. Defaults to true.',
'value' => 'optional',
],
'set-customized' => [
'description' => 'Set all existing translations as being customized. Defaults to true.',
'value' => 'optional',
],
'autocreate-language' => [
'description' => 'Autocreate any imported language if it does not yet exist. Defaults to true.',
'value' => 'optional',
],
],
'aliases' => ['langimp', 'language-import'],
];
$items['language-export-translations'] = [
'description' => 'Export string of a language as one or more .po files',
'examples' => [
'Export all custom translations into the directory defined in $settings[\'custom_translations_directory\'].' => 'drush langexp',
'Export all german translated strings' => 'drush langexp --langcodes=de --status=customized,not-customized --file=all-de.po',
'Export untranslated strings from all languages to current dir' => 'drush langexp --status=untranslated --file=./todo-%langcode.po',
],
'options' => [
'statuses' => [
'description' => "The statuses to export, defaults to 'customized'\nThis can be a comma-separated list of 'customized', 'not-customized', 'not-translated', or (as abbreviation) 'all'.",
'value' => 'optional',
],
'langcodes' => [
'description' => 'The language codes to export, comma-separated. Defaults to all enabled languages.',
'value' => 'optional',
],
'file' => [
'description' => 'The target file pattern. You can use %langcode as placeholder. Defaults to "%language.po". If the path is relative and does not start with ".", $settings[\'custom_translations_directory\'] must be defined and the path is relative to that directory.',
'value' => 'optional',
],
'force' => [
'description' => 'Write file even if no translations. Defaults to true.',
'value' => 'optional',
],
],
'aliases' => ['langexp', 'language-export'],
];
return $items;
}
/**
* Add a language.
*/
function drush_drush_language_language_add() {
$args = func_get_args();
if (count($args) == 0) {
return drush_set_error('DRUSH_LANGUAGE', dt('Please provide one or more language codes as arguments.'));
}
$langcodes = explode(',', $args[0]);
\Drupal::service('drush_language.cli')->add(new Drush8Io(), 'dt', $langcodes);
}
/**
* Assigns the default language.
*/
function drush_drush_language_language_default() {
$args = func_get_args();
if (count($args) == 0) {
return drush_set_error('DRUSH_LANGUAGE', dt('Please provide one or more language codes as arguments.'));
}
foreach ($args as $langcode) {
\Drupal::service('drush_language.cli')->languageDefault(new Drush8Io(), 'dt', $langcode);
}
}
/**
* Imports .po file(s).
*/
function drush_drush_language_language_import_translations() {
// Get arguments and options.
$file_paths = func_get_args();
$options = [
'langcode' => drush_get_option('langcode', NULL),
'replace-customized' => drush_get_option('set-customized', TRUE),
'replace-not-customized' => drush_get_option('replace-customized', TRUE),
'set-customized' => drush_get_option('replace-not-customized', TRUE),
'autocreate-language' => drush_get_option('autocreate-language', TRUE),
];
\Drupal::service('drush_language.cli')->importTranslations(new Drush8Io(), 'dt', $file_paths, $options);
}
/**
* Exports .po file(s).
*/
function drush_drush_language_language_export_translations() {
// Get options.
$options = [
'statuses' => drush_get_option_list('statuses', 'customized'),
'langcodes' => drush_get_option_list('langcodes'),
'file' => drush_get_option('file', '%langcode.po'),
'force' => drush_get_option('force', TRUE),
];
\Drupal::service('drush_language.cli')->exportTranslations(new Drush8Io(), 'dt', $options);
}
name: 'Drush Language commands'
type: module
description: 'Provides Drush commands for language manipulation'
core_version_requirement: ^8.7.7 || ^9 || ^10
package: Multilingual
dependencies:
- drupal:language
# Information added by Drupal.org packaging script on 2022-12-13
version: '8.x-1.0-rc5'
project: 'drush_language'
datestamp: 1670957304
services:
drush_language.cli:
class: Drupal\drush_language\Service\DrushLanguageCliService
arguments:
- '@config.factory'
- '@entity_type.manager'
- '@language_manager'
- '@module_handler'
- '@file_system'
<?php
namespace Drupal\drush_language\Commands;
use Drupal\drush_language\Service\DrushLanguageCliService;
use Drush\Commands\DrushCommands;
use Drush\Utils\StringUtils;
/**
* Implements the Drush language commands.
*/
class DrushLanguageCommands extends DrushCommands {
/**
* The interoperability cli service.
*
* @var \Drupal\drush_language\Service\DrushLanguageCliService
*/
protected $cliService;
/**
* DrushLanguageCommands constructor.
*
* @param \Drupal\drush_language\Service\DrushLanguageCliService $cliService
* The CLI service which allows interoperability.
*/
public function __construct(DrushLanguageCliService $cliService) {
$this->cliService = $cliService;
}
/**
* Add and import one or more new language definitions.
*
* @codingStandardsIgnoreStart
* @command language:add
*
* @param array $langcodes
* A comma-delimited list of langcodes for which a definition will be added.
*
* @aliases langadd,language-add
* @codingStandardsIgnoreEnd
*/
public function add(array $langcodes) {
$langcodes = StringUtils::csvToArray($langcodes);
$this->cliService->add($this->io(), 'dt', $langcodes);
}
/**
* Assign an enabled language as default.
*
* @codingStandardsIgnoreStart
* @command language:default
*
* @param string $langcode
* The langcode of the language which will be set as the default language.
*
* @aliases langdef,language-default
* @codingStandardsIgnoreEnd
*/
public function languageDefault($langcode) {
$this->cliService->languageDefault($this->io(), 'dt', $langcode);
}
// @codingStandardsIgnoreStart
/**
* Import a single .po file.
*
* @command language:import:translations
*
* @param array $poFiles
* Comma-separated list of paths .po files containing the translations.
*
* @option langcode
* Language code to be imported. If not given, extracted from file name.
* @option replace-customized
* Replace existing customized translations. Defaults to true.
* @option replace-not-customized
* Replace existing not-customized translations. Defaults to true.
* @option set-customized
* Set all existing translations as being customized. Defaults to true.
* @option autocreate-language
* Autocreate any imported language if it does not yet exist. Defaults to
* true.
*
* @usage drush langimp
* Import all custom translations from the directory defined in
* $settings['custom_translations_directory'].
* @usage drush langimp --langcode=ru file.po
* Import single file with explicit langcode.
* @usage drush langimp --langcode=eo --no-set-customized --no-replace-customized de.po foomodule.fr.po barmodule-8.x-2.2-rc1.es.po
* Import not-customized (e.g. module) translations, without replacing
* custom translations, with auto langcode (these are the recognized
* patterns)'.
*
* @aliases langimp,language-import,language-import-translations
*/
public function importTranslations(
array $poFiles,
array $options = [
'langcode' => NULL,
'replace-customized' => TRUE,
'replace-not-customized' => TRUE,
'set-customized' => TRUE,
'autocreate-language' => TRUE,
]
) {
$poFiles = StringUtils::csvToArray($poFiles);
$this->cliService->importTranslations($this->io(), 'dt', $poFiles, $options);
}
// @codingStandardsIgnoreEnd
// @codingStandardsIgnoreStart
/**
* Export string of a language as one or more .po files.
*
* @command language:export:translations
*
* @option statuses
* The statuses to export, defaults to 'customized'. This can be a
* comma-separated list of 'customized', 'not-customized', 'not-translated',
* or (as abbreviation) 'all'.
* @option langcodes
* The language codes to export, comma-separated. Defaults to all enabled
* languages.
* @option file
* The target file pattern. You can use %langcode as placeholder. Defaults
* to "%language.po". If the path is relative and does not start with ".",
* $settings[\'custom_translations_directory\'] must be defined and the path
* is relative to that directory.
* @option force
* Write file even if no translations. Defaults to true.
*
* @usage drush langexp
* Export all custom translations into the directory defined in
* $settings['custom_translations_directory'].
* @usage drush langexp --langcodes=de --status=customized,not-customized --file=all-de.po
* Export all german translated strings
* @usage drush langexp --status=untranslated --file=./todo-%langcode.po
* Export untranslated strings from all languages to current dir
*
* @aliases langexp,language-export,language-export-translations
*/
public function exportTranslations($options = [
'statuses' => ['customized'],
'langcodes' => [],
'file' => '%langcode.po',
'force' => TRUE,
]) {
try {
$this->cliService->exportTranslations($this->io(), 'dt', $options);
}
catch (\Exception $exception) {
}
}
// @codingStandardsIgnoreEnd
}
<?php
namespace Drupal\drush_language;
use Drush\Log\LogLevel;
/**
* Class Drush8Io.
*
* This is a stand in for \Symfony\Component\Console\Style\StyleInterface with
* Drush 8 so that we don't need to depend on symfony components.
*/
// @codingStandardsIgnoreStart
class Drush8Io {
public function confirm($text) {
return drush_confirm($text);
}
public function success($text) {
drush_log($text, LogLevel::SUCCESS);
}
public function error($text) {
drush_log($text, LogLevel::ERROR);
}
public function text($text) {
drush_log($text, LogLevel::NOTICE);
}
public function warning($text) {
drush_log($text, LogLevel::WARNING);
}
}
// @codingStandardsIgnoreEnd
<?php
namespace Drupal\Tests\drush_language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Simple browser test.
*
* @group drush_language
*/
class AdminPageTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'drush_language',
];
/**
* Theme to enable.
*
* @var string
*/
protected $defaultTheme = 'stark';
/**
* Tests that the /admin page returns a 200.
*/
public function testAdminPage() {
$this->drupalLogin($this->rootUser);
$this->drupalGet('admin');
$this->assertSession()->statusCodeEquals(200);
// Ensure that the test is not marked as risky because of no assertions.
// see https://gitlab.com/weitzman/drupal-test-traits/-/commit/82bf5059908f9073b3468cb7313960da72176d9a
$this->addToAssertionCount(1);
}
}
Changelog
=========
## UNRELEASED
## 1.11.0
### Added
* Added explicit (non magic) `allNullOr*` methods, with `@psalm-assert` annotations, for better Psalm support.
### Changed
* Trait methods will now check the assertion themselves, instead of using `__callStatic`
* `isList` will now deal correctly with (modified) lists that contain `NaN`
* `reportInvalidArgument` now has a return type of `never`.
### Removed
* Removed `symfony/polyfill-ctype` as a dependency, and require `ext-cytpe` instead.
* You can still require the `symfony/polyfill-ctype` in your project if you need it, as it provides `ext-ctype`
## 1.10.0
### Added
* On invalid assertion, we throw a `Webmozart\Assert\InvalidArgumentException`
* Added `Assert::positiveInteger()`
### Changed
* Using a trait with real implementations of `all*()` and `nullOr*()` methods to improve psalm compatibility.
### Removed
* Support for PHP <7.2
## 1.9.1
## Fixed
* provisional support for PHP 8.0
## 1.9.0
* added better Psalm support for `all*` & `nullOr*` methods
* These methods are now understood by Psalm through a mixin. You may need a newer version of Psalm in order to use this
* added `@psalm-pure` annotation to `Assert::notFalse()`
* added more `@psalm-assert` annotations where appropriate
## Changed
* the `all*` & `nullOr*` methods are now declared on an interface, instead of `@method` annotations.
This interface is linked to the `Assert` class with a `@mixin` annotation. Most IDE's have supported this
for a long time, and you should not lose any autocompletion capabilities. PHPStan has supported this since
version `0.12.20`. This package is marked incompatible (with a composer conflict) with phpstan version prior to that.
If you do not use PHPStan than this does not matter.
## 1.8.0
### Added
* added `Assert::notStartsWith()`
* added `Assert::notEndsWith()`
* added `Assert::inArray()`
* added `@psalm-pure` annotations to pure assertions
### Fixed
* Exception messages of comparisons between `DateTime(Immutable)` objects now display their date & time.
* Custom Exception messages for `Assert::count()` now use the values to render the exception message.
## 1.7.0 (2020-02-14)
### Added
* added `Assert::notFalse()`
* added `Assert::isAOf()`
* added `Assert::isAnyOf()`
* added `Assert::isNotA()`
## 1.6.0 (2019-11-24)
### Added
* added `Assert::validArrayKey()`
* added `Assert::isNonEmptyList()`
* added `Assert::isNonEmptyMap()`
* added `@throws InvalidArgumentException` annotations to all methods that throw.
* added `@psalm-assert` for the list type to the `isList` assertion.
### Fixed
* `ResourceBundle` & `SimpleXMLElement` now pass the `isCountable` assertions.
They are countable, without implementing the `Countable` interface.
* The doc block of `range` now has the proper variables.
* An empty array will now pass `isList` and `isMap`. As it is a valid form of both.
If a non-empty variant is needed, use `isNonEmptyList` or `isNonEmptyMap`.
### Changed
* Removed some `@psalm-assert` annotations, that were 'side effect' assertions See:
* [#144](https://github.com/webmozart/assert/pull/144)
* [#145](https://github.com/webmozart/assert/issues/145)
* [#146](https://github.com/webmozart/assert/pull/146)
* [#150](https://github.com/webmozart/assert/pull/150)
* If you use Psalm, the minimum version needed is `3.6.0`. Which is enforced through a composer conflict.
If you don't use Psalm, then this has no impact.
## 1.5.0 (2019-08-24)
### Added
* added `Assert::uniqueValues()`
* added `Assert::unicodeLetters()`
* added: `Assert::email()`
* added support for [Psalm](https://github.com/vimeo/psalm), by adding `@psalm-assert` annotations where appropriate.
### Fixed
* `Assert::endsWith()` would not give the correct result when dealing with a multibyte suffix.
* `Assert::length(), minLength, maxLength, lengthBetween` would not give the correct result when dealing with multibyte characters.
**NOTE**: These 2 changes may break your assertions if you relied on the fact that multibyte characters didn't behave correctly.
### Changed
* The names of some variables have been updated to better reflect what they are.
* All function calls are now in their FQN form, slightly increasing performance.
* Tests are now properly ran against HHVM-3.30 and PHP nightly.
### Deprecation
* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()`
* This was already done in 1.3.0, but it was only done through a silenced `trigger_error`. It is now annotated as well.
## 1.4.0 (2018-12-25)
### Added
* added `Assert::ip()`
* added `Assert::ipv4()`
* added `Assert::ipv6()`
* added `Assert::notRegex()`
* added `Assert::interfaceExists()`
* added `Assert::isList()`
* added `Assert::isMap()`
* added polyfill for ctype
### Fixed
* Special case when comparing objects implementing `__toString()`
## 1.3.0 (2018-01-29)
### Added
* added `Assert::minCount()`
* added `Assert::maxCount()`
* added `Assert::countBetween()`
* added `Assert::isCountable()`
* added `Assert::notWhitespaceOnly()`
* added `Assert::natural()`
* added `Assert::notContains()`
* added `Assert::isArrayAccessible()`
* added `Assert::isInstanceOfAny()`
* added `Assert::isIterable()`
### Fixed
* `stringNotEmpty` will no longer report "0" is an empty string
### Deprecation
* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()`
## 1.2.0 (2016-11-23)
* added `Assert::throws()`
* added `Assert::count()`
* added extension point `Assert::reportInvalidArgument()` for custom subclasses
## 1.1.0 (2016-08-09)
* added `Assert::object()`
* added `Assert::propertyExists()`
* added `Assert::propertyNotExists()`
* added `Assert::methodExists()`
* added `Assert::methodNotExists()`
* added `Assert::uuid()`
## 1.0.2 (2015-08-24)
* integrated Style CI
* add tests for minimum package dependencies on Travis CI
## 1.0.1 (2015-05-12)
* added support for PHP 5.3.3
## 1.0.0 (2015-05-12)
* first stable release
## 1.0.0-beta (2015-03-19)
* first beta release
The MIT License (MIT)
Copyright (c) 2014 Bernhard Schussek
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This diff is collapsed.
{
"name": "webmozart/assert",
"description": "Assertions to validate method input/output with nice error messages.",
"license": "MIT",
"keywords": [
"assert",
"check",
"validate"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"require": {
"php": "^7.2 || ^8.0",
"ext-ctype": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5.13"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<4.6.1 || 4.6.2"
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Webmozart\\Assert\\Tests\\": "tests/",
"Webmozart\\Assert\\Bin\\": "bin/src"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.10-dev"
}
}
}
This diff is collapsed.
<?php
/*
* This file is part of the webmozart/assert package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\Assert;
class InvalidArgumentException extends \InvalidArgumentException
{
}
This diff is collapsed.
preset: symfony
enabled:
- ordered_use
- strict
disabled:
- empty_return
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
matrix:
include:
- php: 5.3
- php: 5.4
- php: 5.5
- php: 5.6
- php: 5.6
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'
- php: hhvm
- php: nightly
allow_failures:
- php: hhvm
- php: nightly
fast_finish: true
install: composer update $COMPOSER_FLAGS -n
script: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover
after_script:
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi;'
Changelog
=========
* 2.3.0 (2015-12-17)
* added `Url::makeRelative()` for calculating relative paths between URLs
* fixed `Path::makeRelative()` to trim leading dots when moving outside of
the base path
* 2.2.3 (2015-10-05)
* fixed `Path::makeRelative()` to produce `..` when called with the parent
directory of a path
* 2.2.2 (2015-08-24)
* `Path::makeAbsolute()` does not fail anymore if an absolute path is passed
with a different root (partition) than the base path
* 2.2.1 (2015-08-24)
* fixed minimum versions in composer.json
* 2.2.0 (2015-08-14)
* added `Path::normalize()`
* 2.1.0 (2015-07-14)
* `Path::canonicalize()` now turns `~` into the user's home directory on
Unix and Windows 8 or later.
* 2.0.0 (2015-05-21)
* added support for streams, e.g. "phar://C:/path/to/file"
* added `Path::join()`
* all `Path` methods now throw exceptions if parameters with invalid types are
passed
* added an internal buffer to `Path::canonicalize()` in order to increase the
performance of the `Path` class
* 1.1.0 (2015-03-19)
* added `Path::getFilename()`
* added `Path::getFilenameWithoutExtension()`
* added `Path::getExtension()`
* added `Path::hasExtension()`
* added `Path::changeExtension()`
* `Path::makeRelative()` now works when the absolute path and the base path
have equal directory names beneath different base directories
(e.g. "/webmozart/css/style.css" relative to "/puli/css")
* 1.0.2 (2015-01-12)
* `Path::makeAbsolute()` fails now if the base path is not absolute
* `Path::makeRelative()` now works when a relative path is passed and the base
path is empty
* 1.0.1 (2014-12-03)
* Added PHP 5.6 to Travis.
* Fixed bug in `Path::makeRelative()` when first argument is shorter than second
* Made HHVM compatibility mandatory in .travis.yml
* Added PHP 5.3.3 to travis.yml
* 1.0.0 (2014-11-26)
* first release
The MIT License (MIT)
Copyright (c) 2014 Bernhard Schussek
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File Path Utility
=================
[![Build Status](https://travis-ci.org/webmozart/path-util.svg?branch=2.3.0)](https://travis-ci.org/webmozart/path-util)
[![Build status](https://ci.appveyor.com/api/projects/status/d5uuypr6p162gpxf/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/path-util/branch/master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/webmozart/path-util/badges/quality-score.png?b=2.3.0)](https://scrutinizer-ci.com/g/webmozart/path-util/?branch=2.3.0)
[![Latest Stable Version](https://poser.pugx.org/webmozart/path-util/v/stable.svg)](https://packagist.org/packages/webmozart/path-util)
[![Total Downloads](https://poser.pugx.org/webmozart/path-util/downloads.svg)](https://packagist.org/packages/webmozart/path-util)
[![Dependency Status](https://www.versioneye.com/php/webmozart:path-util/2.3.0/badge.svg)](https://www.versioneye.com/php/webmozart:path-util/2.3.0)
Latest release: [2.3.0](https://packagist.org/packages/webmozart/path-util#2.3.0)
PHP >= 5.3.3
This package provides robust, cross-platform utility functions for normalizing,
comparing and modifying file paths and URLs.
Installation
------------
The utility can be installed with [Composer]:
```
$ composer require webmozart/path-util
```
Usage
-----
Use the `Path` class to handle file paths:
```php
use Webmozart\PathUtil\Path;
echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini');
// => /var/www/vhost/config.ini
echo Path::canonicalize('C:\Programs\Webmozart\..\config.ini');
// => C:/Programs/config.ini
echo Path::canonicalize('~/config.ini');
// => /home/webmozart/config.ini
echo Path::makeAbsolute('config/config.yml', '/var/www/project');
// => /var/www/project/config/config.yml
echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads');
// => ../config/config.yml
$paths = array(
'/var/www/vhosts/project/httpdocs/config/config.yml',
'/var/www/vhosts/project/httpdocs/images/banana.gif',
'/var/www/vhosts/project/httpdocs/uploads/../images/nicer-banana.gif',
);
Path::getLongestCommonBasePath($paths);
// => /var/www/vhosts/project/httpdocs
Path::getFilename('/views/index.html.twig');
// => index.html.twig
Path::getFilenameWithoutExtension('/views/index.html.twig');
// => index.html
Path::getFilenameWithoutExtension('/views/index.html.twig', 'html.twig');
Path::getFilenameWithoutExtension('/views/index.html.twig', '.html.twig');
// => index
Path::getExtension('/views/index.html.twig');
// => twig
Path::hasExtension('/views/index.html.twig');
// => true
Path::hasExtension('/views/index.html.twig', 'twig');
// => true
Path::hasExtension('/images/profile.jpg', array('jpg', 'png', 'gif'));
// => true
Path::changeExtension('/images/profile.jpeg', 'jpg');
// => /images/profile.jpg
Path::join('phar://C:/Documents', 'projects/my-project.phar', 'composer.json');
// => phar://C:/Documents/projects/my-project.phar/composer.json
Path::getHomeDirectory();
// => /home/webmozart
```
Use the `Url` class to handle URLs:
```php
use Webmozart\PathUtil\Url;
echo Url::makeRelative('http://example.com/css/style.css', 'http://example.com/puli');
// => ../css/style.css
echo Url::makeRelative('http://cdn.example.com/css/style.css', 'http://example.com/puli');
// => http://cdn.example.com/css/style.css
```
Learn more in the [Documentation] and the [API Docs].
Authors
-------
* [Bernhard Schussek] a.k.a. [@webmozart]
* [The Community Contributors]
Documentation
-------------
Read the [Documentation] if you want to learn more about the contained functions.
Contribute
----------
Contributions are always welcome!
* Report any bugs or issues you find on the [issue tracker].
* You can grab the source code at the [Git repository].
Support
-------
If you are having problems, send a mail to bschussek@gmail.com or shout out to
[@webmozart] on Twitter.
License
-------
All contents of this package are licensed under the [MIT license].
[Bernhard Schussek]: http://webmozarts.com
[The Community Contributors]: https://github.com/webmozart/path-util/graphs/contributors
[Composer]: https://getcomposer.org
[Documentation]: docs/usage.md
[API Docs]: https://webmozart.github.io/path-util/api/latest/class-Webmozart.PathUtil.Path.html
[issue tracker]: https://github.com/webmozart/path-util/issues
[Git repository]: https://github.com/webmozart/path-util
[@webmozart]: https://twitter.com/webmozart
[MIT license]: LICENSE
build: false
shallow_clone: true
platform: x86
clone_folder: c:\projects\webmozart\path-util
cache:
- '%LOCALAPPDATA%\Composer\files'
init:
- SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH%
environment:
matrix:
- COMPOSER_FLAGS: ""
- COMPOSER_FLAGS: --prefer-lowest --prefer-stable
install:
- cinst -y OpenSSL.Light
- cinst -y php
- cd c:\tools\php
- copy php.ini-production php.ini /Y
- echo date.timezone="UTC" >> php.ini
- echo extension_dir=ext >> php.ini
- echo extension=php_openssl.dll >> php.ini
- echo extension=php_mbstring.dll >> php.ini
- echo extension=php_fileinfo.dll >> php.ini
- echo memory_limit=1G >> php.ini
- cd c:\projects\webmozart\path-util
- php -r "readfile('http://getcomposer.org/installer');" | php
- php composer.phar update %COMPOSER_FLAGS% --no-interaction --no-progress
test_script:
- cd c:\projects\webmozart\path-util
- vendor\bin\phpunit.bat --verbose
{
"name": "webmozart/path-util",
"description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.",
"license": "MIT",
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"require": {
"php": ">=5.3.3",
"webmozart/assert": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6",
"sebastian/version": "^1.0.1"
},
"autoload": {
"psr-4": {
"Webmozart\\PathUtil\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Webmozart\\PathUtil\\Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
}
}
Painfree Handling of File Paths
===============================
Dealing with file paths usually involves some difficulties:
* **System Heterogeneity**: File paths look different on different platforms.
UNIX file paths start with a slash ("/"), while Windows file paths start with
a system drive ("C:"). UNIX uses forward slashes, while Windows uses
backslashes by default ("\").
* **Absolute/Relative Paths**: Web applications frequently need to deal with
absolute and relative paths. Converting one to the other properly is tricky
and repetitive.
This package provides few, but robust utility methods to simplify your life
when dealing with file paths.
Canonicalization
----------------
*Canonicalization* is the transformation of a path into a normalized (the
"canonical") format. You can canonicalize a path with `Path::canonicalize()`:
```php
echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini');
// => /var/www/vhost/config.ini
```
The following modifications happen during canonicalization:
* "." segments are removed;
* ".." segments are resolved;
* backslashes ("\") are converted into forward slashes ("/");
* root paths ("/" and "C:/") always terminate with a slash;
* non-root paths never terminate with a slash;
* schemes (such as "phar://") are kept;
* replace "~" with the user's home directory.
You can pass absolute paths and relative paths to `canonicalize()`. When a
relative path is passed, ".." segments at the beginning of the path are kept:
```php
echo Path::canonicalize('../uploads/../config/config.yml');
// => ../config/config.yml
```
Malformed paths are returned unchanged:
```php
echo Path::canonicalize('C:Programs/PHP/php.ini');
// => C:Programs/PHP/php.ini
```
Converting Absolute/Relative Paths
----------------------------------
Absolute/relative paths can be converted with the methods `Path::makeAbsolute()`
and `Path::makeRelative()`.
`makeAbsolute()` expects a relative path and a base path to base that relative
path upon:
```php
echo Path::makeAbsolute('config/config.yml', '/var/www/project');
// => /var/www/project/config/config.yml
```
If an absolute path is passed in the first argument, the absolute path is
returned unchanged:
```php
echo Path::makeAbsolute('/usr/share/lib/config.ini', '/var/www/project');
// => /usr/share/lib/config.ini
```
The method resolves ".." segments, if there are any:
```php
echo Path::makeAbsolute('../config/config.yml', '/var/www/project/uploads');
// => /var/www/project/config/config.yml
```
This method is very useful if you want to be able to accept relative paths (for
example, relative to the root directory of your project) and absolute paths at
the same time.
`makeRelative()` is the inverse operation to `makeAbsolute()`:
```php
echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project');
// => config/config.yml
```
If the path is not within the base path, the method will prepend ".." segments
as necessary:
```php
echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads');
// => ../config/config.yml
```
Use `isAbsolute()` and `isRelative()` to check whether a path is absolute or
relative:
```php
Path::isAbsolute('C:\Programs\PHP\php.ini')
// => true
```
All four methods internally canonicalize the passed path.
Finding Longest Common Base Paths
---------------------------------
When you store absolute file paths on the file system, this leads to a lot of
duplicated information:
```php
return array(
'/var/www/vhosts/project/httpdocs/config/config.yml',
'/var/www/vhosts/project/httpdocs/config/routing.yml',
'/var/www/vhosts/project/httpdocs/config/services.yml',
'/var/www/vhosts/project/httpdocs/images/banana.gif',
'/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif',
);
```
Especially when storing many paths, the amount of duplicated information is
noticeable. You can use `Path::getLongestCommonBasePath()` to check a list of
paths for a common base path:
```php
$paths = array(
'/var/www/vhosts/project/httpdocs/config/config.yml',
'/var/www/vhosts/project/httpdocs/config/routing.yml',
'/var/www/vhosts/project/httpdocs/config/services.yml',
'/var/www/vhosts/project/httpdocs/images/banana.gif',
'/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif',
);
Path::getLongestCommonBasePath($paths);
// => /var/www/vhosts/project/httpdocs
```
Use this path together with `Path::makeRelative()` to shorten the stored paths:
```php
$bp = '/var/www/vhosts/project/httpdocs';
return array(
$bp.'/config/config.yml',
$bp.'/config/routing.yml',
$bp.'/config/services.yml',
$bp.'/images/banana.gif',
$bp.'/uploads/images/nicer-banana.gif',
);
```
`getLongestCommonBasePath()` always returns canonical paths.
Use `Path::isBasePath()` to test whether a path is a base path of another path:
```php
Path::isBasePath("/var/www", "/var/www/project");
// => true
Path::isBasePath("/var/www", "/var/www/project/..");
// => true
Path::isBasePath("/var/www", "/var/www/project/../..");
// => false
```
Finding Directories/Root Directories
------------------------------------
PHP offers the function `dirname()` to obtain the directory path of a file path.
This method has a few quirks:
* `dirname()` does not accept backslashes on UNIX
* `dirname("C:/Programs")` returns "C:", not "C:/"
* `dirname("C:/")` returns ".", not "C:/"
* `dirname("C:")` returns ".", not "C:/"
* `dirname("Programs")` returns ".", not ""
* `dirname()` does not canonicalize the result
`Path::getDirectory()` fixes these shortcomings:
```php
echo Path::getDirectory("C:\Programs");
// => C:/
```
Additionally, you can use `Path::getRoot()` to obtain the root of a path:
```php
echo Path::getRoot("/etc/apache2/sites-available");
// => /
echo Path::getRoot("C:\Programs\Apache\Config");
// => C:/
```
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="Path-Util Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<!-- Whitelist for code coverage -->
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
</phpunit>
This diff is collapsed.
<?php
/*
* This file is part of the webmozart/path-util package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\PathUtil;
use InvalidArgumentException;
use Webmozart\Assert\Assert;
/**
* Contains utility methods for handling URL strings.
*
* The methods in this class are able to deal with URLs.
*
* @since 2.3
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Claudio Zizza <claudio@budgegeria.de>
*/
final class Url
{
/**
* Turns a URL into a relative path.
*
* The result is a canonical path. This class is using functionality of Path class.
*
* @see Path
*
* @param string $url A URL to make relative.
* @param string $baseUrl A base URL.
*
* @return string
*
* @throws InvalidArgumentException If the URL and base URL does
* not match.
*/
public static function makeRelative($url, $baseUrl)
{
Assert::string($url, 'The URL must be a string. Got: %s');
Assert::string($baseUrl, 'The base URL must be a string. Got: %s');
Assert::contains($baseUrl, '://', '%s is not an absolute Url.');
list($baseHost, $basePath) = self::split($baseUrl);
if (false === strpos($url, '://')) {
if (0 === strpos($url, '/')) {
$host = $baseHost;
} else {
$host = '';
}
$path = $url;
} else {
list($host, $path) = self::split($url);
}
if ('' !== $host && $host !== $baseHost) {
throw new InvalidArgumentException(sprintf(
'The URL "%s" cannot be made relative to "%s" since their host names are different.',
$host,
$baseHost
));
}
return Path::makeRelative($path, $basePath);
}
/**
* Splits a URL into its host and the path.
*
* ```php
* list ($root, $path) = Path::split("http://example.com/webmozart")
* // => array("http://example.com", "/webmozart")
*
* list ($root, $path) = Path::split("http://example.com")
* // => array("http://example.com", "")
* ```
*
* @param string $url The URL to split.
*
* @return string[] An array with the host and the path of the URL.
*
* @throws InvalidArgumentException If $url is not a URL.
*/
private static function split($url)
{
$pos = strpos($url, '://');
$scheme = substr($url, 0, $pos + 3);
$url = substr($url, $pos + 3);
if (false !== ($pos = strpos($url, '/'))) {
$host = substr($url, 0, $pos);
$url = substr($url, $pos);
} else {
// No path, only host
$host = $url;
$url = '/';
}
// At this point, we have $scheme, $host and $path
$root = $scheme.$host;
return array($root, $url);
}
}
This diff is collapsed.
<?php
/*
* This file is part of the webmozart/path-util package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\PathUtil\Tests;
use Webmozart\PathUtil\Url;
/**
* @since 2.3
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Claudio Zizza <claudio@budgegeria.de>
*/
class UrlTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideMakeRelativeTests
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelative($absolutePath, $basePath, $relativePath)
{
$host = 'http://example.com';
$relative = Url::makeRelative($host.$absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
$relative = Url::makeRelative($absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
}
/**
* @dataProvider provideMakeRelativeIsAlreadyRelativeTests
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeIsAlreadyRelative($absolutePath, $basePath, $relativePath)
{
$host = 'http://example.com';
$relative = Url::makeRelative($absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
}
/**
* @dataProvider provideMakeRelativeTests
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeWithFullUrl($absolutePath, $basePath, $relativePath)
{
$host = 'ftp://user:password@example.com:8080';
$relative = Url::makeRelative($host.$absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The URL must be a string. Got: array
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfInvalidUrl()
{
Url::makeRelative(array(), 'http://example.com/webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base URL must be a string. Got: array
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfInvalidBaseUrl()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', array());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage "webmozart/puli" is not an absolute Url.
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfBaseUrlNoUrl()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage "" is not an absolute Url.
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfBaseUrlEmpty()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', '');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base URL must be a string. Got: NULL
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfBaseUrlNull()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', null);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The URL "http://example.com" cannot be made relative to "http://example2.com" since
* their host names are different.
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfDifferentDomains()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'http://example2.com/webmozart/puli');
}
public function provideMakeRelativeTests()
{
return array(
array('/webmozart/puli/css/style.css', '/webmozart/puli', 'css/style.css'),
array('/webmozart/puli/css/style.css?key=value&key2=value', '/webmozart/puli', 'css/style.css?key=value&key2=value'),
array('/webmozart/puli/css/style.css?key[]=value&key[]=value', '/webmozart/puli', 'css/style.css?key[]=value&key[]=value'),
array('/webmozart/css/style.css', '/webmozart/puli', '../css/style.css'),
array('/css/style.css', '/webmozart/puli', '../../css/style.css'),
array('/', '/', ''),
// relative to root
array('/css/style.css', '/', 'css/style.css'),
// same sub directories in different base directories
array('/puli/css/style.css', '/webmozart/css', '../../puli/css/style.css'),
array('/webmozart/puli/./css/style.css', '/webmozart/puli', 'css/style.css'),
array('/webmozart/puli/../css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/.././css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/./../css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/../../css/style.css', '/webmozart/puli', '../../css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/./puli', 'css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/../puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/./../puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/.././puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/../../puli', '../webmozart/puli/css/style.css'),
// first argument shorter than second
array('/css', '/webmozart/puli', '../../css'),
// second argument shorter than first
array('/webmozart/puli', '/css', '../webmozart/puli'),
array('', '', ''),
);
}
public function provideMakeRelativeIsAlreadyRelativeTests()
{
return array(
array('css/style.css', '/webmozart/puli', 'css/style.css'),
array('css/style.css', '', 'css/style.css'),
array('css/../style.css', '', 'style.css'),
array('css/./style.css', '', 'css/style.css'),
array('../style.css', '/', 'style.css'),
array('./style.css', '/', 'style.css'),
array('../../style.css', '/', 'style.css'),
array('../../style.css', '', 'style.css'),
array('./style.css', '', 'style.css'),
array('../style.css', '', 'style.css'),
array('./../style.css', '', 'style.css'),
array('css/./../style.css', '', 'style.css'),
array('css//style.css', '', 'css/style.css'),
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment