From 5a6be8ae9c818c358424c95939cf14f302a289ca Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 10 Jan 2017 16:13:23 -0800 Subject: [PATCH] [Dotenv] added the component --- composer.json | 1 + src/Symfony/Component/Dotenv/.gitignore | 3 + src/Symfony/Component/Dotenv/CHANGELOG.md | 7 + src/Symfony/Component/Dotenv/Dotenv.php | 338 ++++++++++++++++++ .../Dotenv/Exception/ExceptionInterface.php | 21 ++ .../Dotenv/Exception/FormatException.php | 34 ++ .../Exception/FormatExceptionContext.php | 49 +++ .../Dotenv/Exception/PathException.php | 25 ++ src/Symfony/Component/Dotenv/LICENSE | 19 + src/Symfony/Component/Dotenv/README.md | 14 + .../Component/Dotenv/Tests/DotenvTest.php | 147 ++++++++ src/Symfony/Component/Dotenv/composer.json | 36 ++ src/Symfony/Component/Dotenv/phpunit.xml.dist | 28 ++ 13 files changed, 722 insertions(+) create mode 100644 src/Symfony/Component/Dotenv/.gitignore create mode 100644 src/Symfony/Component/Dotenv/CHANGELOG.md create mode 100644 src/Symfony/Component/Dotenv/Dotenv.php create mode 100644 src/Symfony/Component/Dotenv/Exception/ExceptionInterface.php create mode 100644 src/Symfony/Component/Dotenv/Exception/FormatException.php create mode 100644 src/Symfony/Component/Dotenv/Exception/FormatExceptionContext.php create mode 100644 src/Symfony/Component/Dotenv/Exception/PathException.php create mode 100644 src/Symfony/Component/Dotenv/LICENSE create mode 100644 src/Symfony/Component/Dotenv/README.md create mode 100644 src/Symfony/Component/Dotenv/Tests/DotenvTest.php create mode 100644 src/Symfony/Component/Dotenv/composer.json create mode 100644 src/Symfony/Component/Dotenv/phpunit.xml.dist diff --git a/composer.json b/composer.json index acfbd0d3c29d1..5fdff2193785c 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "symfony/debug-bundle": "self.version", "symfony/doctrine-bridge": "self.version", "symfony/dom-crawler": "self.version", + "symfony/dotenv": "self.version", "symfony/event-dispatcher": "self.version", "symfony/expression-language": "self.version", "symfony/filesystem": "self.version", diff --git a/src/Symfony/Component/Dotenv/.gitignore b/src/Symfony/Component/Dotenv/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Dotenv/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Dotenv/CHANGELOG.md b/src/Symfony/Component/Dotenv/CHANGELOG.md new file mode 100644 index 0000000000000..2204282c26ca6 --- /dev/null +++ b/src/Symfony/Component/Dotenv/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +3.3.0 +----- + + * added the component diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php new file mode 100644 index 0000000000000..0b485f3f21005 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -0,0 +1,338 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv; + +use Symfony\Component\Dotenv\Exception\FormatException; +use Symfony\Component\Dotenv\Exception\FormatExceptionContext; +use Symfony\Component\Dotenv\Exception\PathException; +use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException; +use Symfony\Component\Process\Process; + +/** + * Manages .env files. + * + * @author Fabien Potencier + */ +final class Dotenv +{ + const VARNAME_REGEX = '(?i:[A-Z][A-Z0-9_]*+)'; + const STATE_VARNAME = 0; + const STATE_VALUE = 1; + + private $path; + private $cursor; + private $lineno; + private $data; + private $end; + private $state; + private $values; + + /** + * Loads one or several .env files. + * + * @param ...string A list of files to load + * + * @throws FormatException when a file has a syntax error + * @throws PathException when a file does not exist or is not readable + */ + public function load(/*...$paths*/) + { + // func_get_args() to be replaced by a variadic argument for Symfony 4.0 + foreach (func_get_args() as $path) { + if (!is_readable($path)) { + throw new PathException($path); + } + + $this->populate($this->parse(file_get_contents($path), $path)); + } + } + + /** + * Sets values as environment variables (via putenv, $_ENV, and $_SERVER). + * + * Note that existing environment variables are never overridden. + * + * @param array An array of env variables + */ + public function populate($values) + { + foreach ($values as $name => $value) { + if (isset($_ENV[$name]) || false !== getenv($name)) { + continue; + } + + putenv("$name=$value"); + $_ENV[$name] = $value; + $_SERVER[$name] = $value; + } + } + + /** + * Parses the contents of an .env file. + * + * @param string $data The data to be parsed + * @param string $path The original file name where data where stored (used for more meaningful error messages) + * + * @return array An array of env variables + * + * @throws FormatException when a file has a syntax error + */ + public function parse($data, $path = '.env') + { + $this->path = $path; + $this->data = str_replace(array("\r\n", "\r"), "\n", $data); + $this->lineno = 1; + $this->cursor = 0; + $this->end = strlen($this->data); + $this->state = self::STATE_VARNAME; + $this->values = array(); + $name = $value = ''; + + $this->skipEmptyLines(); + + while ($this->cursor < $this->end) { + switch ($this->state) { + case self::STATE_VARNAME: + $name = $this->lexVarname(); + $this->state = self::STATE_VALUE; + break; + + case self::STATE_VALUE: + $this->values[$name] = $this->lexValue(); + $this->state = self::STATE_VARNAME; + break; + } + } + + if (self::STATE_VALUE === $this->state) { + $this->values[$name] = ''; + } + + try { + return $this->values; + } finally { + $this->values = array(); + } + } + + private function lexVarname() + { + // var name + optional export + if (!preg_match('/(export[ \t]++)?('.self::VARNAME_REGEX.')/A', $this->data, $matches, 0, $this->cursor)) { + throw $this->createFormatException('Invalid character in variable name'); + } + $this->moveCursor($matches[0]); + + if ($this->cursor === $this->end || "\n" === $this->data[$this->cursor] || '#' === $this->data[$this->cursor]) { + if ($matches[1]) { + throw $this->createFormatException('Unable to unset an environment variable'); + } + + throw $this->createFormatException('Missing = in the environment variable declaration'); + } + + if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) { + throw $this->createFormatException('Whitespace are not supported after the variable name'); + } + + if ('=' !== $this->data[$this->cursor]) { + throw $this->createFormatException('Missing = in the environment variable declaration'); + } + ++$this->cursor; + + return $matches[2]; + } + + private function lexValue() + { + if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, null, $this->cursor)) { + $this->moveCursor($matches[0]); + $this->skipEmptyLines(); + + return ''; + } + + if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) { + throw $this->createFormatException('Whitespace are not supported before the value'); + } + + $value = ''; + $singleQuoted = false; + $notQuoted = false; + if ("'" === $this->data[$this->cursor]) { + $singleQuoted = true; + ++$this->cursor; + while ("\n" !== $this->data[$this->cursor]) { + if ("'" === $this->data[$this->cursor]) { + if ($this->cursor + 1 === $this->end) { + break; + } + if ("'" !== $this->data[$this->cursor + 1]) { + break; + } + + ++$this->cursor; + } + $value .= $this->data[$this->cursor]; + ++$this->cursor; + + if ($this->cursor === $this->end) { + throw $this->createFormatException('Missing quote to end the value'); + } + } + if ("\n" === $this->data[$this->cursor]) { + throw $this->createFormatException('Missing quote to end the value'); + } + ++$this->cursor; + } elseif ('"' === $this->data[$this->cursor]) { + ++$this->cursor; + while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) { + $value .= $this->data[$this->cursor]; + ++$this->cursor; + + if ($this->cursor === $this->end) { + throw $this->createFormatException('Missing quote to end the value'); + } + } + if ("\n" === $this->data[$this->cursor]) { + throw $this->createFormatException('Missing quote to end the value'); + } + ++$this->cursor; + $value = str_replace(array('\\\\', '\\"', '\r', '\n'), array('\\', '"', "\r", "\n"), $value); + } else { + $notQuoted = true; + $prevChr = $this->data[$this->cursor - 1]; + while ($this->cursor < $this->end && "\n" !== $this->data[$this->cursor] && !((' ' === $prevChr || "\t" === $prevChr) && '#' === $this->data[$this->cursor])) { + $value .= $prevChr = $this->data[$this->cursor]; + ++$this->cursor; + } + $value = rtrim($value); + } + + $this->skipEmptyLines(); + + $currentValue = $value; + if (!$singleQuoted) { + $value = $this->resolveVariables($value); + $value = $this->resolveCommands($value); + } + + if ($notQuoted && $currentValue == $value && preg_match('/\s+/', $value)) { + throw $this->createFormatException('A value containing spaces must be surrounded by quotes'); + } + + return $value; + } + + private function skipWhitespace() + { + $this->cursor += strspn($this->data, " \t", $this->cursor); + } + + private function skipEmptyLines() + { + if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, null, $this->cursor)) { + $this->moveCursor($match[0]); + } + } + + private function resolveCommands($value) + { + if (false === strpos($value, '$')) { + return $value; + } + + $regex = '/ + (\\\\)? # escaped with a backslash? + \$ + (? + \( # require opening parenthesis + ([^()]|\g)+ # allow any number of non-parens, or balanced parens (by nesting the expression recursively) + \) # require closing paren + ) + /x'; + + return preg_replace_callback($regex, function ($matches) { + if ('\\' === $matches[1]) { + return substr($matches[0], 1); + } + + if ('\\' === DIRECTORY_SEPARATOR) { + throw new \LogicException('Resolving commands is not supported on Windows.'); + } + + if (!class_exists(Process::class)) { + throw new \LogicException('Resolving commands requires the Symfony Process component.'); + } + + $process = new Process('echo '.$matches[0]); + $process->inheritEnvironmentVariables(true); + $process->setEnv($this->values); + try { + $process->mustRun(); + } catch (ProcessException $e) { + throw $this->createFormatException(sprintf('Issue expanding a command (%s)', $process->getErrorOutput())); + } + + return preg_replace('/[\r\n]+$/', '', $process->getOutput()); + }, $value); + } + + private function resolveVariables($value) + { + if (false === strpos($value, '$')) { + return $value; + } + + $regex = '/ + (\\\\)? # escaped with a backslash? + \$ + (?!\() # no opening parenthesis + (\{)? # optional brace + ('.self::VARNAME_REGEX.') # var name + (\})? # optional closing brace + /x'; + + $value = preg_replace_callback($regex, function ($matches) { + if ('\\' === $matches[1]) { + return substr($matches[0], 1); + } + + if ('{' === $matches[2] && !isset($matches[4])) { + throw $this->createFormatException('Unclosed braces on variable expansion'); + } + + $name = $matches[3]; + $value = isset($this->values[$name]) ? $this->values[$name] : (isset($_ENV[$name]) ? isset($_ENV[$name]) : (string) getenv($name)); + + if (!$matches[2] && isset($matches[4])) { + $value .= '}'; + } + + return $value; + }, $value); + + // unescape $ + return str_replace('\\$', '$', $value); + } + + private function moveCursor($text) + { + $this->cursor += strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + private function createFormatException($message) + { + return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor)); + } +} diff --git a/src/Symfony/Component/Dotenv/Exception/ExceptionInterface.php b/src/Symfony/Component/Dotenv/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000..90509f7db5b17 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * Interface for exceptions. + * + * @author Fabien Potencier + */ +interface ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Dotenv/Exception/FormatException.php b/src/Symfony/Component/Dotenv/Exception/FormatException.php new file mode 100644 index 0000000000000..26f1442b695a5 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Exception/FormatException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * Thrown when a file has a syntax error. + * + * @author Fabien Potencier + */ +final class FormatException extends \LogicException implements ExceptionInterface +{ + private $context; + + public function __construct($message, FormatExceptionContext $context, $code = 0, \Exception $previous = null) + { + $this->context = $context; + + parent::__construct(sprintf("%s in \"%s\" at line %d.\n%s", $message, $context->getPath(), $context->getLineno(), $context->getDetails()), $code, $previous); + } + + public function getContext() + { + return $this->context; + } +} diff --git a/src/Symfony/Component/Dotenv/Exception/FormatExceptionContext.php b/src/Symfony/Component/Dotenv/Exception/FormatExceptionContext.php new file mode 100644 index 0000000000000..70d2ef11df3fb --- /dev/null +++ b/src/Symfony/Component/Dotenv/Exception/FormatExceptionContext.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * @author Fabien Potencier + */ +final class FormatExceptionContext +{ + private $data; + private $path; + private $lineno; + private $cursor; + + public function __construct($data, $path, $lineno, $cursor) + { + $this->data = $data; + $this->path = $path; + $this->lineno = $lineno; + $this->cursor = $cursor; + } + + public function getPath() + { + return $this->path; + } + + public function getLineno() + { + return $this->lineno; + } + + public function getDetails() + { + $before = str_replace("\n", '\n', substr($this->data, max(0, $this->cursor - 20), min(20, $this->cursor))); + $after = str_replace("\n", '\n', substr($this->data, $this->cursor, 20)); + + return '...'.$before.$after."...\n".str_repeat(' ', strlen($before) + 2).'^ line '.$this->lineno.' offset '.$this->cursor; + } +} diff --git a/src/Symfony/Component/Dotenv/Exception/PathException.php b/src/Symfony/Component/Dotenv/Exception/PathException.php new file mode 100644 index 0000000000000..ac4d540ff4831 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Exception/PathException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Exception; + +/** + * Thrown when a file does not exist or is not readable. + * + * @author Fabien Potencier + */ +final class PathException extends \RuntimeException implements ExceptionInterface +{ + public function __construct($path, $code = 0, \Exception $previous = null) + { + parent::__construct(sprintf('Unable to read the "%s" environment file.', $path), $code, $previous); + } +} diff --git a/src/Symfony/Component/Dotenv/LICENSE b/src/Symfony/Component/Dotenv/LICENSE new file mode 100644 index 0000000000000..ce39894f6a9a2 --- /dev/null +++ b/src/Symfony/Component/Dotenv/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Fabien Potencier + +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. diff --git a/src/Symfony/Component/Dotenv/README.md b/src/Symfony/Component/Dotenv/README.md new file mode 100644 index 0000000000000..eae83b99ae5d9 --- /dev/null +++ b/src/Symfony/Component/Dotenv/README.md @@ -0,0 +1,14 @@ +Dotenv Component +================ + +Symfony Dotenv parses `.env` files to make environment variables stored in them +accessible via `getenv()`, `$_ENV`, or `$_SERVER`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/dotenv/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Dotenv/Tests/DotenvTest.php b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php new file mode 100644 index 0000000000000..1fbc5086769f6 --- /dev/null +++ b/src/Symfony/Component/Dotenv/Tests/DotenvTest.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Tests; + +use Symfony\Component\Dotenv\Dotenv; +use Symfony\Component\Dotenv\Exception\FormatException; + +class DotenvTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getEnvDataWithFormatErrors + */ + public function testParseWithFormatError($data, $error) + { + $dotenv = new Dotenv(); + + try { + $dotenv->parse($data); + $this->fail('Should throw a FormatException'); + } catch (FormatException $e) { + $this->assertStringMatchesFormat($error, $e->getMessage()); + } + } + + public function getEnvDataWithFormatErrors() + { + $tests = array( + array('FOO=BAR BAZ', "A value containing spaces must be surrounded by quotes in \".env\" at line 1.\n...FOO=BAR BAZ...\n ^ line 1 offset 11"), + array('FOO BAR=BAR', "Whitespace are not supported after the variable name in \".env\" at line 1.\n...FOO BAR=BAR...\n ^ line 1 offset 3"), + array('FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"), + array('FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"), + array('FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"), + array('export FOO', "Unable to unset an environment variable in \".env\" at line 1.\n...export FOO...\n ^ line 1 offset 10"), + array('FOO=${FOO', "Unclosed braces on variable expansion in \".env\" at line 1.\n...FOO=\${FOO...\n ^ line 1 offset 9"), + ); + + if ('\\' !== DIRECTORY_SEPARATOR) { + $tests[] = array('FOO=$((1dd2))', "Issue expanding a command (%s\n) in \".env\" at line 1.\n...FOO=$((1dd2))...\n ^ line 1 offset 13"); + } + + return $tests; + } + + /** + * @dataProvider getEnvData + */ + public function testParse($data, $expected) + { + $dotenv = new Dotenv(); + $this->assertSame($expected, $dotenv->parse($data)); + } + + public function getEnvData() + { + putenv('LOCAL=local'); + + $tests = array( + // spaces + array('FOO=bar', array('FOO' => 'bar')), + array(' FOO=bar ', array('FOO' => 'bar')), + array('FOO=', array('FOO' => '')), + array("FOO=\n\n\nBAR=bar", array('FOO' => '', 'BAR' => 'bar')), + array('FOO= ', array('FOO' => '')), + array("FOO=\nBAR=bar", array('FOO' => '', 'BAR' => 'bar')), + + // newlines + array("\n\nFOO=bar\r\n\n", array('FOO' => 'bar')), + array("FOO=bar\r\nBAR=foo", array('FOO' => 'bar', 'BAR' => 'foo')), + array("FOO=bar\rBAR=foo", array('FOO' => 'bar', 'BAR' => 'foo')), + array("FOO=bar\nBAR=foo", array('FOO' => 'bar', 'BAR' => 'foo')), + + // quotes + array("FOO=\"bar\"\n", array('FOO' => 'bar')), + array("FOO=\"bar'foo\"\n", array('FOO' => 'bar\'foo')), + array("FOO='bar'\n", array('FOO' => 'bar')), + array("FOO='bar\"foo'\n", array('FOO' => 'bar"foo')), + array("FOO=\"bar\\\"foo\"\n", array('FOO' => 'bar"foo')), + array("FOO='bar''foo'\n", array('FOO' => 'bar\'foo')), + array('FOO="bar\nfoo"', array('FOO' => "bar\nfoo")), + array('FOO="bar\rfoo"', array('FOO' => "bar\rfoo")), + array('FOO=\'bar\nfoo\'', array('FOO' => 'bar\nfoo')), + array('FOO=\'bar\rfoo\'', array('FOO' => 'bar\rfoo')), + array('FOO=" FOO "', array('FOO' => ' FOO ')), + array('FOO=" "', array('FOO' => ' ')), + array('PATH="c:\\\\"', array('PATH' => 'c:\\')), + array("FOO=\"bar\nfoo\"", array('FOO' => "bar\nfoo")), + + // concatenated values + + // comments + array("#FOO=bar\nBAR=foo", array('BAR' => 'foo')), + array("#FOO=bar # Comment\nBAR=foo", array('BAR' => 'foo')), + array("FOO='bar foo' # Comment", array('FOO' => 'bar foo')), + array("FOO='bar#foo' # Comment", array('FOO' => 'bar#foo')), + array("# Comment\r\nFOO=bar\n# Comment\nBAR=foo", array('FOO' => 'bar', 'BAR' => 'foo')), + array("FOO=bar # Another comment\nBAR=foo", array('FOO' => 'bar', 'BAR' => 'foo')), + array("FOO=\n\n# comment\nBAR=bar", array('FOO' => '', 'BAR' => 'bar')), + array('FOO=NOT#COMMENT', array('FOO' => 'NOT#COMMENT')), + array('FOO= # Comment', array('FOO' => '')), + + // edge cases (no conversions, only strings as values) + array('FOO=0', array('FOO' => '0')), + array('FOO=false', array('FOO' => 'false')), + array('FOO=null', array('FOO' => 'null')), + + // export + array('export FOO=bar', array('FOO' => 'bar')), + array(' export FOO=bar', array('FOO' => 'bar')), + + // variable expansion + array("FOO=BAR\nBAR=\$FOO", array('FOO' => 'BAR', 'BAR' => 'BAR')), + array("FOO=BAR\nBAR=\"\$FOO\"", array('FOO' => 'BAR', 'BAR' => 'BAR')), + array("FOO=BAR\nBAR='\$FOO'", array('FOO' => 'BAR', 'BAR' => '$FOO')), + array("FOO_BAR9=BAR\nBAR=\$FOO_BAR9", array('FOO_BAR9' => 'BAR', 'BAR' => 'BAR')), + array("FOO=BAR\nBAR=\${FOO}Z", array('FOO' => 'BAR', 'BAR' => 'BARZ')), + array("FOO=BAR\nBAR=\$FOO}", array('FOO' => 'BAR', 'BAR' => 'BAR}')), + array("FOO=BAR\nBAR=\\\$FOO", array('FOO' => 'BAR', 'BAR' => '$FOO')), + array('FOO=" \\$ "', array('FOO' => ' $ ')), + array('FOO=" $ "', array('FOO' => ' $ ')), + array('BAR=$LOCAL', array('BAR' => 'local')), + array('FOO=$NOTDEFINED', array('FOO' => '')), + ); + + if ('\\' !== DIRECTORY_SEPARATOR) { + $tests = array_merge($tests, array( + // command expansion + array('FOO=$(echo foo)', array('FOO' => 'foo')), + array('FOO=$((1+2))', array('FOO' => '3')), + array('FOO=FOO$((1+2))BAR', array('FOO' => 'FOO3BAR')), + array('FOO=$(echo "$(echo "$(echo "$(echo foo)")")")', array('FOO' => 'foo')), + array("FOO=$(echo \"Quotes won't be a problem\")", array('FOO' => 'Quotes won\'t be a problem')), + array("FOO=bar\nBAR=$(echo \"FOO is \$FOO\")", array('FOO' => 'bar', 'BAR' => 'FOO is bar')), + )); + } + + return $tests; + } +} diff --git a/src/Symfony/Component/Dotenv/composer.json b/src/Symfony/Component/Dotenv/composer.json new file mode 100644 index 0000000000000..020342bdd4a96 --- /dev/null +++ b/src/Symfony/Component/Dotenv/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/dotenv", + "type": "library", + "description": "Registers environment variables from a .env file", + "keywords": ["environment", "env", "dotenv"], + "homepage": "https://symfony.com", + "license" : "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/process": "^3.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Dotenv\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/src/Symfony/Component/Dotenv/phpunit.xml.dist b/src/Symfony/Component/Dotenv/phpunit.xml.dist new file mode 100644 index 0000000000000..3f436a4eff51d --- /dev/null +++ b/src/Symfony/Component/Dotenv/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + +