Skip to content

Commit aac14ff

Browse files
committed
fix(parser): support bash-style single-quote splices in values
1 parent fa4e6a9 commit aac14ff

File tree

3 files changed

+27
-2
lines changed

3 files changed

+27
-2
lines changed

src/dotenv/parser.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]:
2323
_unquoted_key = make_regex(r"([^=\#\s]+)")
2424
_equal_sign = make_regex(r"(=[^\S\r\n]*)")
2525
_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'")
26+
_single_quote_splice = make_regex('"\'"')
2627
_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"')
2728
_unquoted_value = make_regex(r"([^\r\n]*)")
2829
_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?")
@@ -125,11 +126,23 @@ def parse_unquoted_value(reader: Reader) -> str:
125126
return re.sub(r"\s+#.*", "", part).rstrip()
126127

127128

129+
def parse_single_quoted_value(reader: Reader) -> str:
130+
splice_value = '"\'"'
131+
value = ""
132+
while True:
133+
(part,) = reader.read_regex(_single_quoted_value)
134+
value += decode_escapes(_single_quote_escapes, part)
135+
if reader.peek(len(splice_value)) != splice_value:
136+
break
137+
reader.read_regex(_single_quote_splice)
138+
value += "'"
139+
return value
140+
141+
128142
def parse_value(reader: Reader) -> str:
129143
char = reader.peek(1)
130144
if char == "'":
131-
(value,) = reader.read_regex(_single_quoted_value)
132-
return decode_escapes(_single_quote_escapes, value)
145+
return parse_single_quoted_value(reader)
133146
elif char == '"':
134147
(value,) = reader.read_regex(_double_quoted_value)
135148
return decode_escapes(_double_quote_escapes, value)

tests/test_main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ def test_dotenv_values_file(dotenv_path):
663663
# With quotes
664664
({"b": "c"}, 'a="${b}"', True, {"a": "c"}),
665665
({"b": "c"}, "a='${b}'", True, {"a": "c"}),
666+
({}, "a='b'\"'\"'c'", True, {"a": "b'c"}),
666667
# With surrounding text
667668
({"b": "c"}, "a=x${b}y", True, {"a": "xcy"}),
668669
# Self-referential

tests/test_parser.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,17 @@
295295
)
296296
],
297297
),
298+
(
299+
"a='b'\"'\"'c'",
300+
[
301+
Binding(
302+
key="a",
303+
value="b'c",
304+
original=Original(string="a='b'\"'\"'c'", line=1),
305+
error=False,
306+
)
307+
],
308+
),
298309
(
299310
"a=à",
300311
[

0 commit comments

Comments
 (0)