From fe2608dfed9831c3aca39788d8dea7e4a150692a Mon Sep 17 00:00:00 2001 From: Kamil Kozik Date: Tue, 10 Mar 2026 17:51:37 +0100 Subject: [PATCH 1/2] SerializationOptions - add an option to strip string quotes --- cli/hcl_to_json.py | 6 +++++ hcl2/rules/strings.py | 18 +++++++++----- hcl2/utils.py | 4 +++ test/unit/rules/test_strings.py | 44 +++++++++++++++++++++++++++++++++ test/unit/test_api.py | 20 +++++++++++++++ 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/cli/hcl_to_json.py b/cli/hcl_to_json.py index 7e9f7275..889a402b 100644 --- a/cli/hcl_to_json.py +++ b/cli/hcl_to_json.py @@ -85,6 +85,11 @@ def main(): action="store_true", help="Convert scientific notation to standard floats", ) + parser.add_argument( + "--strip-string-quotes", + action="store_true", + help="Strip surrounding double-quotes from serialized string values", + ) # JSON output formatting parser.add_argument( @@ -106,6 +111,7 @@ def main(): preserve_heredocs=not args.no_preserve_heredocs, force_operation_parentheses=args.force_parens, preserve_scientific_notation=not args.no_preserve_scientific, + strip_string_quotes=args.strip_string_quotes, ) json_indent = args.json_indent diff --git a/hcl2/rules/strings.py b/hcl2/rules/strings.py index 2a19a0f9..35e6feea 100644 --- a/hcl2/rules/strings.py +++ b/hcl2/rules/strings.py @@ -92,11 +92,10 @@ def serialize( self, options=SerializationOptions(), context=SerializationContext() ) -> Any: """Serialize to a quoted string.""" - return ( - '"' - + "".join(part.serialize(options, context) for part in self.string_parts) - + '"' - ) + inner = "".join(part.serialize(options, context) for part in self.string_parts) + if options.strip_string_quotes: + return inner + return '"' + inner + '"' class HeredocTemplateRule(LarkRule): @@ -129,9 +128,13 @@ def serialize( heredoc = ( heredoc.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") ) + if options.strip_string_quotes: + return heredoc return f'"{heredoc}"' result = heredoc.rstrip(self._trim_chars) + if options.strip_string_quotes: + return result return f'"{result}"' @@ -178,4 +181,7 @@ def serialize( lines = [line.replace("\\", "\\\\").replace('"', '\\"') for line in lines] sep = "\\n" if not options.preserve_heredocs else "\n" - return '"' + sep.join(lines) + '"' + inner = sep.join(lines) + if options.strip_string_quotes: + return inner + return '"' + inner + '"' diff --git a/hcl2/utils.py b/hcl2/utils.py index 7e349558..c09fd0d1 100644 --- a/hcl2/utils.py +++ b/hcl2/utils.py @@ -19,6 +19,10 @@ class SerializationOptions: preserve_heredocs: bool = True force_operation_parentheses: bool = False preserve_scientific_notation: bool = True + # Remove surrounding double-quotes from serialized string values, + # producing backwards-compatible output (e.g. "hello" instead of '"hello"'). + # Note: round-trip through from_dict/dumps is NOT supported WITH this option. + strip_string_quotes: bool = False @dataclass diff --git a/test/unit/rules/test_strings.py b/test/unit/rules/test_strings.py index b037d997..e142de5b 100644 --- a/test/unit/rules/test_strings.py +++ b/test/unit/rules/test_strings.py @@ -159,6 +159,26 @@ def test_serialize_only_interpolation(self): rule = _make_string([_make_string_part_interpolation("x")]) self.assertEqual(rule.serialize(), '"${x}"') + def test_serialize_strip_string_quotes(self): + rule = _make_string([_make_string_part_chars("hello")]) + opts = SerializationOptions(strip_string_quotes=True) + self.assertEqual(rule.serialize(opts), "hello") + + def test_serialize_strip_string_quotes_empty(self): + rule = _make_string([]) + opts = SerializationOptions(strip_string_quotes=True) + self.assertEqual(rule.serialize(opts), "") + + def test_serialize_strip_string_quotes_with_interpolation(self): + rule = _make_string( + [ + _make_string_part_chars("prefix:"), + _make_string_part_interpolation("var.name"), + ] + ) + opts = SerializationOptions(strip_string_quotes=True) + self.assertEqual(rule.serialize(opts), "prefix:${var.name}") + # --- HeredocTemplateRule tests --- @@ -237,6 +257,18 @@ def test_serialize_no_preserve_invalid_raises(self): with self.assertRaises(RuntimeError): rule.serialize(opts) + def test_serialize_strip_string_quotes_preserve(self): + token = HEREDOC_TEMPLATE("< Date: Tue, 10 Mar 2026 18:02:45 +0100 Subject: [PATCH 2/2] add descriptions to each option of SerializerOptions, DeserializerOptions and FormatterOptions --- cli/hcl_to_json.py | 4 ++-- hcl2/deserializer.py | 6 ++++++ hcl2/formatter.py | 10 +++++++++- hcl2/utils.py | 13 +++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/cli/hcl_to_json.py b/cli/hcl_to_json.py index 889a402b..dde8cfa0 100644 --- a/cli/hcl_to_json.py +++ b/cli/hcl_to_json.py @@ -68,7 +68,7 @@ def main(): parser.add_argument( "--no-explicit-blocks", action="store_true", - help="Disable explicit block markers", + help="Disable explicit block markers. Note: round-trip through json_to_hcl is NOT supported with this option.", ) parser.add_argument( "--no-preserve-heredocs", @@ -88,7 +88,7 @@ def main(): parser.add_argument( "--strip-string-quotes", action="store_true", - help="Strip surrounding double-quotes from serialized string values", + help="Strip surrounding double-quotes from serialized string values. Note: round-trip through json_to_hcl is NOT supported with this option.", ) # JSON output formatting diff --git a/hcl2/deserializer.py b/hcl2/deserializer.py index 5043985a..5e282503 100644 --- a/hcl2/deserializer.py +++ b/hcl2/deserializer.py @@ -63,9 +63,15 @@ class DeserializerOptions: """Options controlling how Python dicts are deserialized into LarkElement trees.""" + # Convert heredoc values (<