From 1156c6ec1f3afa0c025c8851f1a2e5ae0db7c688 Mon Sep 17 00:00:00 2001 From: Alexander Mancevice Date: Wed, 3 Oct 2018 13:08:05 -0400 Subject: [PATCH 1/3] IPython version constraints IPython 7+ only works with Python 3.5+ IPython 6+ only works with Python 3+ IPython 5 works with Python 2 --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 90608e6f..9a56e8a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,7 @@ bumpversion wheel pytest-cov click -ipython +ipython<6; python_version <= '2' +ipython<7; python_version <= '3.3' +ipython; python_version > '3.3' pypandoc From c0d409eb12baeff614243e94119e633dbdcbb839 Mon Sep 17 00:00:00 2001 From: Alexander Mancevice Date: Wed, 3 Oct 2018 13:24:48 -0400 Subject: [PATCH 2/3] Jedi version constraint Jedi v0.13 drops support for Python 3.3 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 9a56e8a2..f8547ac1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ bumpversion wheel pytest-cov click +jedi<0.13; python_version <= '3.3' ipython<6; python_version <= '2' ipython<7; python_version <= '3.3' ipython; python_version > '3.3' From 889da50a5fa8969456460410b92b275e3bae47ec Mon Sep 17 00:00:00 2001 From: Alexander Mancevice Date: Wed, 3 Oct 2018 12:36:13 -0400 Subject: [PATCH 3/3] Allow multi-line ENV values Use regex to parse ENV variable names from .env --- dotenv/main.py | 19 ++++++++++++------- tests/test_cli.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/dotenv/main.py b/dotenv/main.py index e6040e49..760f3bc4 100644 --- a/dotenv/main.py +++ b/dotenv/main.py @@ -14,7 +14,7 @@ from .compat import StringIO __escape_decoder = codecs.getdecoder('unicode_escape') -__posix_variable = re.compile('\$\{[^\}]*\}') +__posix_variable = re.compile(r'\$\{[^\}]*\}') def decode_escaped(escaped): @@ -78,12 +78,17 @@ def dict(self): def parse(self): f = self._get_stream() - for line in f: - key, value = parse_line(line) - if not key: - continue - - yield key, value + fbody = f.read() + # key starts with either `export FOO`, or `FOO` + key = r'^(?:export )?([a-zA-Z_][a-zA-Z0-9_]+)' + # `=` may be padded with spaces + eql = r' *= *' + # val is either wrapped in quotes, or a string of non-white-space chars + val = r'(?:\'(.*?)\'|"(.*?)"|([^\s]+))' + envs = re.findall(key + eql + val, fbody, re.MULTILINE | re.DOTALL) + for env in envs: + # Yield the key and the first non-empty val + yield env[0], next(x for x in env[1:] if x) if self._is_file: f.close() diff --git a/tests/test_cli.py b/tests/test_cli.py index 6c9187ec..eff038a9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import tempfile from os import environ from os.path import dirname, join @@ -95,6 +96,18 @@ def test_value_with_special_characters(): sh.rm(dotenv_path) +def test_multiline_value(): + with tempfile.NamedTemporaryFile() as f: + f.write("TEST='line 1\nline 2'".encode('utf-8')) + f.flush() + assert dotenv.get_key(f.name, 'TEST') == "line 1\nline 2" + + with tempfile.NamedTemporaryFile() as f: + f.write("TEST='line 1\nline 2'\nFIZZ=BUZZ".encode('utf-8')) + f.flush() + assert dotenv.get_key(f.name, 'TEST') == "line 1\nline 2" + + def test_unset(): sh.touch(dotenv_path) success, key_to_set, value_to_set = dotenv.set_key(dotenv_path, 'HELLO', 'WORLD')