diff --git a/pyhocon/converter.py b/pyhocon/converter.py index 158437f..bb93e5c 100644 --- a/pyhocon/converter.py +++ b/pyhocon/converter.py @@ -115,7 +115,10 @@ def to_hocon(cls, config, compact=False, indent=2, level=0): lines += '\n'.join(bet_lines) lines += '\n{indent}]'.format(indent=''.rjust((level - 1) * indent, ' ')) elif isinstance(config, str): - if '\n' in config and len(config) > 1: + # A triple-quoted literal cannot contain the `"""` sequence (it + # would terminate the literal early), so such strings must use the + # escaped single-quoted form instead. + if '\n' in config and len(config) > 1 and '"""' not in config: lines = '"""{value}"""'.format(value=config) # multilines else: lines = '"{value}"'.format(value=cls._escape_string(config)) @@ -127,7 +130,7 @@ def to_hocon(cls, config, compact=False, indent=2, level=0): lines += '?' lines += config.variable + '}' + config.ws elif isinstance(config, ConfigQuotedString): - if '\n' in config.value and len(config.value) > 1: + if '\n' in config.value and len(config.value) > 1 and '"""' not in config.value: lines = '"""{value}"""'.format(value=config.value) # multilines else: lines = '"{value}"'.format(value=cls._escape_string(config.value)) diff --git a/tests/test_converter.py b/tests/test_converter.py index d4d80b0..f991393 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- from datetime import timedelta -from pyhocon import ConfigTree +from pyhocon import ConfigFactory, ConfigTree from pyhocon.converter import HOCONConverter @@ -108,6 +108,16 @@ def test_format_multiline_string(self): assert 'a = """b\n"""' == to_hocon({'a': 'b\n'}) assert 'a = """\n\n"""' == to_hocon({'a': '\n\n'}) + def test_format_multiline_string_with_triple_quote(self): + # A multiline string that itself contains `"""` cannot use the + # triple-quoted form (the embedded `"""` would terminate the literal + # early), so it must fall back to the escaped single-quoted form and + # still round-trip. + assert r'a = "a\nb\"\"\"c"' == to_hocon({'a': 'a\nb"""c'}) + for value in ('a\nb"""c', 'before\n"""middle"""\nafter', '"""\nleading'): + parsed = ConfigFactory.parse_string(to_hocon({'a': value}))['a'] + assert parsed == value + def test_format_time_delta(self): for time_delta, expected_result in ((timedelta(days=0), 'td = 0 seconds'), (timedelta(days=5), 'td = 5 days'),