Browse Source

Merge branch 'master' into 2.0

Jordi Boggiano 6 years ago
parent
commit
0b928b2a42

+ 11 - 0
.editorconfig

@@ -0,0 +1,11 @@
+root = true
+
+[*]
+charset = utf-8
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.yml]
+indent_size = 2

+ 28 - 31
.travis.yml

@@ -1,7 +1,5 @@
 language: php
 
-sudo: false
-
 dist: trusty
 
 git:
@@ -16,54 +14,53 @@ addons:
     packages:
       - parallel
 
-php:
-  - 5.4
-  - 5.5
-  - 5.6
-  - 7.0
-  - 7.1
-  - 7.2
-  - 7.3
-  - nightly
-
 matrix:
   include:
     - php: 5.3
       dist: precise
+    - php: 5.4
+    - php: 5.5
+    - php: 5.6
+    - php: 7.0
+    - php: 7.1
+    - php: 7.2
+    - php: 7.3
     - php: 7.3
       env: deps=high
+    - php: nightly
   fast_finish: true
   allow_failures:
     - php: nightly
 
 before_install:
-    # disable xdebug if available
-    - phpenv config-rm xdebug.ini || echo "xdebug not available"
-    # disable default memory limit
-    - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
-    - echo memory_limit = -1 >> $INI
+  # disable xdebug if available
+  - phpenv config-rm xdebug.ini || echo "xdebug not available"
+  # disable default memory limit
+  - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
+  - echo memory_limit = -1 >> $INI
+  - composer validate
 
 install:
-    # flags to pass to install
-    - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress"
-    # update deps to latest in case of high deps build
-    - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi
-    # install dependencies using system provided composer binary
-    - composer install $flags
-    # install dependencies using composer from source
-    - bin/composer install $flags
+  # flags to pass to install
+  - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress"
+  # update deps to latest in case of high deps build
+  - if [ "$deps" == "high" ]; then composer config platform.php 7.2.4; composer update $flags; fi
+  # install dependencies using system provided composer binary
+  - composer install $flags
+  # install dependencies using composer from source
+  - bin/composer install $flags
 
 before_script:
-    # make sure git tests do not complain about user/email not being set
-    - git config --global user.name travis-ci
-    - git config --global user.email travis@example.com
+  # make sure git tests do not complain about user/email not being set
+  - git config --global user.name travis-ci
+  - git config --global user.email travis@example.com
 
 script:
-    # run test suite directories in parallel using GNU parallel
-    - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
+  # run test suite directories in parallel using GNU parallel
+  - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
 
 before_deploy:
-    - php -d phar.readonly=0 bin/compile
+  - php -d phar.readonly=0 bin/compile
 
 deploy:
   provider: releases

+ 5 - 0
composer.json

@@ -73,8 +73,13 @@
         "bin/composer"
     ],
     "scripts": {
+        "compile": "@php -dphar.readonly=0 bin/compile",
         "test": "phpunit"
     },
+    "scripts-descriptions": {
+        "compile": "Compile composer.phar",
+        "test": "Run all tests"
+    },
     "support": {
         "issues": "https://github.com/composer/composer/issues",
         "irc": "irc://irc.freenode.org/composer"

+ 5 - 5
composer.lock

@@ -8,16 +8,16 @@
     "packages": [
         {
             "name": "composer/ca-bundle",
-            "version": "1.1.3",
+            "version": "1.1.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/ca-bundle.git",
-                "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660"
+                "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660",
-                "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660",
+                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
+                "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
                 "shasum": ""
             },
             "require": {
@@ -60,7 +60,7 @@
                 "ssl",
                 "tls"
             ],
-            "time": "2018-10-18T06:09:13+00:00"
+            "time": "2019-01-28T09:30:10+00:00"
         },
         {
             "name": "composer/semver",

+ 69 - 69
doc/03-cli.md

@@ -796,58 +796,40 @@ COMPOSER=composer-other.json php composer.phar install
 
 The generated lock file will use the same name: `composer-other.lock` in this example.
 
-### COMPOSER_ROOT_VERSION
+### COMPOSER_ALLOW_SUPERUSER
 
-By setting this var you can specify the version of the root package, if it can
-not be guessed from VCS info and is not present in `composer.json`.
+If set to 1, this env disables the warning about running commands as root/super user.
+It also disables automatic clearing of sudo sessions, so you should really only set this
+if you use Composer as super user at all times like in docker containers.
 
-### COMPOSER_VENDOR_DIR
+### COMPOSER_AUTH
 
-By setting this var you can make Composer install the dependencies into a
-directory other than `vendor`.
+The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable.
+The contents of the variable should be a JSON formatted object containing http-basic,
+github-oauth, bitbucket-oauth, ... objects as needed, and following the
+[spec from the config](06-config.md#gitlab-oauth).
 
 ### COMPOSER_BIN_DIR
 
 By setting this option you can change the `bin` ([Vendor Binaries](articles/vendor-binaries.md))
 directory to something other than `vendor/bin`.
 
-### http_proxy or HTTP_PROXY
-
-If you are using Composer from behind an HTTP proxy, you can use the standard
-`http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy.
-Many operating systems already set this variable for you.
-
-Using `http_proxy` (lowercased) or even defining both might be preferable since
-some tools like git or curl will only use the lower-cased `http_proxy` version.
-Alternatively you can also define the git proxy using
-`git config --global http.proxy <proxy url>`.
-
-If you are using Composer in a non-CLI context (i.e. integration into a CMS or
-similar use case), and need to support proxies, please provide the `CGI_HTTP_PROXY`
-environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further
-details.
-
-### no_proxy or NO_PROXY
+### COMPOSER_CACHE_DIR
 
-If you are behind a proxy and would like to disable it for certain domains, you
-can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of
-domains the proxy should *not* be used for.
+The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory,
+which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option.
 
-The env var accepts domains, IP addresses, and IP address blocks in CIDR
-notation. You can restrict the filter to a particular port (e.g. `:80`). You
-can also set it to `*` to ignore the proxy for all HTTP requests.
+By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and
+`C:\Users\<user>\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows.
 
-### HTTP_PROXY_REQUEST_FULLURI
+### COMPOSER_CAFILE
 
-If you use a proxy but it does not support the request_fulluri flag, then you
-should set this env var to `false` or `0` to prevent Composer from setting the
-request_fulluri option.
+By setting this environmental value, you can set a path to a certificate bundle
+file to be used during SSL/TLS peer verification.
 
-### HTTPS_PROXY_REQUEST_FULLURI
+### COMPOSER_DISCARD_CHANGES
 
-If you use a proxy but it does not support the request_fulluri flag for HTTPS
-requests, then you should set this env var to `false` or `0` to prevent Composer
-from setting the request_fulluri option.
+This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
 
 ### COMPOSER_HOME
 
@@ -873,60 +855,78 @@ This file allows you to set [repositories](05-repositories.md) and
 In case global configuration matches _local_ configuration, the _local_
 configuration in the project's `composer.json` always wins.
 
-### COMPOSER_CACHE_DIR
+### COMPOSER_HTACCESS_PROTECT
 
-The `COMPOSER_CACHE_DIR` var allows you to change the Composer cache directory,
-which is also configurable via the [`cache-dir`](06-config.md#cache-dir) option.
+Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the
+composer home, cache, and data directories.
 
-By default it points to `$COMPOSER_HOME/cache` on \*nix and macOS, and
-`C:\Users\<user>\AppData\Local\Composer` (or `%LOCALAPPDATA%/Composer`) on Windows.
+### COMPOSER_MEMORY_LIMIT
+
+If set, the value is used as php's memory_limit.
+
+### COMPOSER_MIRROR_PATH_REPOS
+
+If set to 1, this env changes the default path repository strategy to `mirror` instead
+of `symlink`. As it is the default strategy being set it can still be overwritten by
+repository options.
+
+### COMPOSER_NO_INTERACTION
+
+If set to 1, this env var will make Composer behave as if you passed the
+`--no-interaction` flag to every command. This can be set on build boxes/CI.
 
 ### COMPOSER_PROCESS_TIMEOUT
 
 This env var controls the time Composer waits for commands (such as git
 commands) to finish executing. The default value is 300 seconds (5 minutes).
 
-### COMPOSER_CAFILE
-
-By setting this environmental value, you can set a path to a certificate bundle
-file to be used during SSL/TLS peer verification.
+### COMPOSER_ROOT_VERSION
 
-### COMPOSER_AUTH
+By setting this var you can specify the version of the root package, if it can
+not be guessed from VCS info and is not present in `composer.json`.
 
-The `COMPOSER_AUTH` var allows you to set up authentication as an environment variable.
-The contents of the variable should be a JSON formatted object containing http-basic,
-github-oauth, bitbucket-oauth, ... objects as needed, and following the
-[spec from the config](06-config.md#gitlab-oauth).
+### COMPOSER_VENDOR_DIR
 
-### COMPOSER_DISCARD_CHANGES
+By setting this var you can make Composer install the dependencies into a
+directory other than `vendor`.
 
-This env var controls the [`discard-changes`](06-config.md#discard-changes) config option.
+### http_proxy or HTTP_PROXY
 
-### COMPOSER_NO_INTERACTION
+If you are using Composer from behind an HTTP proxy, you can use the standard
+`http_proxy` or `HTTP_PROXY` env vars. Simply set it to the URL of your proxy.
+Many operating systems already set this variable for you.
 
-If set to 1, this env var will make Composer behave as if you passed the
-`--no-interaction` flag to every command. This can be set on build boxes/CI.
+Using `http_proxy` (lowercased) or even defining both might be preferable since
+some tools like git or curl will only use the lower-cased `http_proxy` version.
+Alternatively you can also define the git proxy using
+`git config --global http.proxy <proxy url>`.
 
-### COMPOSER_ALLOW_SUPERUSER
+If you are using Composer in a non-CLI context (i.e. integration into a CMS or
+similar use case), and need to support proxies, please provide the `CGI_HTTP_PROXY`
+environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further
+details.
 
-If set to 1, this env disables the warning about running commands as root/super user.
-It also disables automatic clearing of sudo sessions, so you should really only set this
-if you use Composer as super user at all times like in docker containers.
+### HTTP_PROXY_REQUEST_FULLURI
 
-### COMPOSER_MEMORY_LIMIT
+If you use a proxy but it does not support the request_fulluri flag, then you
+should set this env var to `false` or `0` to prevent Composer from setting the
+request_fulluri option.
 
-If set, the value is used as php's memory_limit.
+### HTTPS_PROXY_REQUEST_FULLURI
 
-### COMPOSER_MIRROR_PATH_REPOS
+If you use a proxy but it does not support the request_fulluri flag for HTTPS
+requests, then you should set this env var to `false` or `0` to prevent Composer
+from setting the request_fulluri option.
 
-If set to 1, this env changes the default path repository strategy to `mirror` instead
-of `symlink`. As it is the default strategy being set it can still be overwritten by
-repository options.
+### no_proxy or NO_PROXY
 
-### COMPOSER_HTACCESS_PROTECT
+If you are behind a proxy and would like to disable it for certain domains, you
+can use the `no_proxy` or `NO_PROXY` env var. Simply set it to a comma separated list of
+domains the proxy should *not* be used for.
 
-Defaults to `1`. If set to `0`, Composer will not create `.htaccess` files in the
-composer home, cache, and data directories.
+The env var accepts domains, IP addresses, and IP address blocks in CIDR
+notation. You can restrict the filter to a particular port (e.g. `:80`). You
+can also set it to `*` to ignore the proxy for all HTTP requests.
 
 ### COMPOSER_DISABLE_NETWORK
 

+ 3 - 2
phpunit.xml.dist

@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<phpunit backupGlobals="false"
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
+         backupGlobals="false"
          backupStaticAttributes="false"
          colors="true"
          convertErrorsToExceptions="true"
@@ -8,7 +10,6 @@
          convertWarningsToExceptions="true"
          processIsolation="false"
          stopOnFailure="false"
-         syntaxCheck="false"
          bootstrap="tests/bootstrap.php"
 >
     <testsuites>

+ 1 - 0
src/Composer/Command/RunScriptCommand.php

@@ -48,6 +48,7 @@ class RunScriptCommand extends BaseCommand
     {
         $this
             ->setName('run-script')
+            ->setAliases(array('run'))
             ->setDescription('Runs the scripts defined in composer.json.')
             ->setDefinition(array(
                 new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'),

+ 7 - 1
src/Composer/Config.php

@@ -216,7 +216,6 @@ class Config
             case 'cache-vcs-dir':
             case 'cafile':
             case 'capath':
-            case 'htaccess-protect':
                 // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                 $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
 
@@ -230,6 +229,13 @@ class Config
 
                 return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val);
 
+            case 'htaccess-protect':
+                $value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT');
+                if (false === $value) {
+                    $value = $this->config[$key];
+                }
+                return $value !== 'false' && (bool) $value;
+
             case 'cache-ttl':
                 return (int) $this->config[$key];
 

+ 5 - 1
src/Composer/Console/Application.php

@@ -17,6 +17,8 @@ use Composer\Util\Platform;
 use Composer\Util\Silencer;
 use Symfony\Component\Console\Application as BaseApplication;
 use Symfony\Component\Console\Exception\CommandNotFoundException;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Helper\QuestionHelper;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
@@ -111,7 +113,9 @@ class Application extends BaseApplication
     {
         $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
 
-        $io = $this->io = new ConsoleIO($input, $output, $this->getHelperSet());
+        $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
+            new QuestionHelper(),
+        )));
         ErrorHandler::register($io);
 
         // switch working dir

+ 6 - 0
src/Composer/EventDispatcher/EventDispatcher.php

@@ -244,6 +244,12 @@ class EventDispatcher
 
                 if (substr($exec, 0, 5) === '@php ') {
                     $exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5);
+                } else {
+                    $finder = new PhpExecutableFinder();
+                    $phpPath = $finder->find(false);
+                    if ($phpPath) {
+                        putenv('PHP_BINARY=' . $phpPath);
+                    }
                 }
 
                 if (0 !== ($exitCode = $this->process->execute($exec))) {

+ 10 - 10
src/Composer/Factory.php

@@ -165,6 +165,16 @@ class Factory
             'data-dir' => self::getDataDir($home),
         )));
 
+        // load global config
+        $file = new JsonFile($config->get('home').'/config.json');
+        if ($file->exists()) {
+            if ($io && $io->isDebug()) {
+                $io->writeError('Loading config file ' . $file->getPath());
+            }
+            $config->merge($file->read());
+        }
+        $config->setConfigSource(new JsonConfigSource($file));
+
         $htaccessProtect = (bool) $config->get('htaccess-protect');
         if ($htaccessProtect) {
             // Protect directory against web access. Since HOME could be
@@ -181,16 +191,6 @@ class Factory
             }
         }
 
-        // load global config
-        $file = new JsonFile($config->get('home').'/config.json');
-        if ($file->exists()) {
-            if ($io && $io->isDebug()) {
-                $io->writeError('Loading config file ' . $file->getPath());
-            }
-            $config->merge($file->read());
-        }
-        $config->setConfigSource(new JsonConfigSource($file));
-
         // load global auth file
         $file = new JsonFile($config->get('home').'/auth.json');
         if ($file->exists()) {

+ 28 - 1
src/Composer/IO/BufferIO.php

@@ -12,8 +12,10 @@
 
 namespace Composer\IO;
 
+use Symfony\Component\Console\Helper\QuestionHelper;
 use Symfony\Component\Console\Output\StreamOutput;
 use Symfony\Component\Console\Formatter\OutputFormatterInterface;
+use Symfony\Component\Console\Input\StreamableInputInterface;
 use Symfony\Component\Console\Input\StringInput;
 use Symfony\Component\Console\Helper\HelperSet;
 
@@ -34,7 +36,9 @@ class BufferIO extends ConsoleIO
 
         $output = new StreamOutput(fopen('php://memory', 'rw'), $verbosity, $formatter ? $formatter->isDecorated() : false, $formatter);
 
-        parent::__construct($input, $output, new HelperSet(array()));
+        parent::__construct($input, $output, new HelperSet(array(
+            new QuestionHelper(),
+        )));
     }
 
     public function getOutput()
@@ -56,4 +60,27 @@ class BufferIO extends ConsoleIO
 
         return $output;
     }
+
+    public function setUserInputs(array $inputs)
+    {
+        if (!$this->input instanceof StreamableInputInterface) {
+            throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.');
+        }
+
+        $this->input->setStream($this->createStream($inputs));
+        $this->input->setInteractive(true);
+    }
+
+    private function createStream(array $inputs)
+    {
+        $stream = fopen('php://memory', 'r+', false);
+
+        foreach ($inputs as $input) {
+            fwrite($stream, $input.PHP_EOL);
+        }
+
+        rewind($stream);
+
+        return $stream;
+    }
 }

+ 1 - 1
src/Composer/Installer/BinaryInstaller.php

@@ -197,7 +197,7 @@ class BinaryInstaller
 dir=\$(cd "\${0%[/\\\\]*}" > /dev/null; cd $binDir && pwd)
 
 if [ -d /proc/cygdrive ] && [[ \$(which php) == \$(readlink -n /proc/cygdrive)/* ]]; then
-   # We are in Cgywin using Windows php, so the path must be translated
+   # We are in Cygwin using Windows php, so the path must be translated
    dir=\$(cygpath -m "\$dir");
 fi
 

+ 7 - 2
src/Composer/Json/JsonFile.php

@@ -34,6 +34,8 @@ class JsonFile
     const JSON_PRETTY_PRINT = 128;
     const JSON_UNESCAPED_UNICODE = 256;
 
+    const COMPOSER_SCHEMA_PATH = '/../../../res/composer-schema.json';
+
     private $path;
     private $httpDownloader;
     private $io;
@@ -144,10 +146,11 @@ class JsonFile
      * Validates the schema of the current json file according to composer-schema.json rules
      *
      * @param  int                     $schema a JsonFile::*_SCHEMA constant
+     * @param  string|null             $schemaFile a path to the schema file
      * @throws JsonValidationException
      * @return bool                    true on success
      */
-    public function validateSchema($schema = self::STRICT_SCHEMA)
+    public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = null)
     {
         $content = file_get_contents($this->path);
         $data = json_decode($content);
@@ -156,7 +159,9 @@ class JsonFile
             self::validateSyntax($content, $this->path);
         }
 
-        $schemaFile = __DIR__ . '/../../../res/composer-schema.json';
+        if (null === $schemaFile) {
+            $schemaFile = __DIR__ . self::COMPOSER_SCHEMA_PATH;
+        }
 
         // Prepend with file:// only when not using a special schema already (e.g. in the phar)
         if (false === strpos($schemaFile, '://')) {

+ 9 - 4
src/Composer/Package/Archiver/ArchiveManager.php

@@ -150,10 +150,15 @@ class ArchiveManager
             $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid();
             $filesystem->ensureDirectoryExists($sourcePath);
 
-            // Download sources
-            $promise = $this->downloadManager->download($package, $sourcePath);
-            $this->loop->wait(array($promise));
-            $this->downloadManager->install($package, $sourcePath);
+            try {
+                // Download sources
+                $promise = $this->downloadManager->download($package, $sourcePath);
+                $this->loop->wait(array($promise));
+                $this->downloadManager->install($package, $sourcePath);
+            } catch (\Exception $e) {
+                $filesystem->removeDirectory($sourcePath);
+                throw  $e;
+            }
 
             // Check exclude from downloaded composer.json
             if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {

+ 43 - 0
tests/Composer/Test/IO/BufferIOTest.php

@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\IO;
+
+use Composer\IO\BufferIO;
+use Composer\Test\TestCase;
+use Symfony\Component\Console\Input\StreamableInputInterface;
+
+class BufferIOTest extends TestCase
+{
+    public function testSetUserInputs()
+    {
+        $bufferIO = new BufferIO();
+
+        $refl = new \ReflectionProperty($bufferIO, 'input');
+        $refl->setAccessible(true);
+        $input = $refl->getValue($bufferIO);
+
+        if (!$input instanceof StreamableInputInterface) {
+            $this->setExpectedException('\RuntimeException', 'Setting the user inputs requires at least the version 3.2 of the symfony/console component.');
+        }
+
+        $bufferIO->setUserInputs(array(
+            'yes',
+            'no',
+            '',
+        ));
+
+        $this->assertTrue($bufferIO->askConfirmation('Please say yes!', 'no'));
+        $this->assertFalse($bufferIO->askConfirmation('Now please say no!', 'yes'));
+        $this->assertSame('default', $bufferIO->ask('Empty string last', 'default'));
+    }
+}