Skip to content

Commit 3efcd37

Browse files
SerializationOptions - add an option to strip string quotes
1 parent 4911962 commit 3efcd37

5 files changed

Lines changed: 86 additions & 6 deletions

File tree

cli/hcl_to_json.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ def main():
8585
action="store_true",
8686
help="Convert scientific notation to standard floats",
8787
)
88+
parser.add_argument(
89+
"--strip-string-quotes",
90+
action="store_true",
91+
help="Strip surrounding double-quotes from serialized string values",
92+
)
8893

8994
# JSON output formatting
9095
parser.add_argument(
@@ -106,6 +111,7 @@ def main():
106111
preserve_heredocs=not args.no_preserve_heredocs,
107112
force_operation_parentheses=args.force_parens,
108113
preserve_scientific_notation=not args.no_preserve_scientific,
114+
strip_string_quotes=args.strip_string_quotes,
109115
)
110116
json_indent = args.json_indent
111117

hcl2/rules/strings.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,10 @@ def serialize(
9292
self, options=SerializationOptions(), context=SerializationContext()
9393
) -> Any:
9494
"""Serialize to a quoted string."""
95-
return (
96-
'"'
97-
+ "".join(part.serialize(options, context) for part in self.string_parts)
98-
+ '"'
99-
)
95+
inner = "".join(part.serialize(options, context) for part in self.string_parts)
96+
if options.strip_string_quotes:
97+
return inner
98+
return '"' + inner + '"'
10099

101100

102101
class HeredocTemplateRule(LarkRule):
@@ -129,9 +128,13 @@ def serialize(
129128
heredoc = (
130129
heredoc.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
131130
)
131+
if options.strip_string_quotes:
132+
return heredoc
132133
return f'"{heredoc}"'
133134

134135
result = heredoc.rstrip(self._trim_chars)
136+
if options.strip_string_quotes:
137+
return result
135138
return f'"{result}"'
136139

137140

@@ -178,4 +181,7 @@ def serialize(
178181
lines = [line.replace("\\", "\\\\").replace('"', '\\"') for line in lines]
179182

180183
sep = "\\n" if not options.preserve_heredocs else "\n"
181-
return '"' + sep.join(lines) + '"'
184+
inner = sep.join(lines)
185+
if options.strip_string_quotes:
186+
return inner
187+
return '"' + inner + '"'

hcl2/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ class SerializationOptions:
1919
preserve_heredocs: bool = True
2020
force_operation_parentheses: bool = False
2121
preserve_scientific_notation: bool = True
22+
# Remove surrounding double-quotes from serialized string values,
23+
# producing backwards-compatible output (e.g. "hello" instead of '"hello"').
24+
# Note: round-trip through from_dict/dumps is NOT supported WITH this option.
25+
strip_string_quotes: bool = False
2226

2327

2428
@dataclass

test/unit/rules/test_strings.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,26 @@ def test_serialize_only_interpolation(self):
159159
rule = _make_string([_make_string_part_interpolation("x")])
160160
self.assertEqual(rule.serialize(), '"${x}"')
161161

162+
def test_serialize_strip_string_quotes(self):
163+
rule = _make_string([_make_string_part_chars("hello")])
164+
opts = SerializationOptions(strip_string_quotes=True)
165+
self.assertEqual(rule.serialize(opts), "hello")
166+
167+
def test_serialize_strip_string_quotes_empty(self):
168+
rule = _make_string([])
169+
opts = SerializationOptions(strip_string_quotes=True)
170+
self.assertEqual(rule.serialize(opts), "")
171+
172+
def test_serialize_strip_string_quotes_with_interpolation(self):
173+
rule = _make_string(
174+
[
175+
_make_string_part_chars("prefix:"),
176+
_make_string_part_interpolation("var.name"),
177+
]
178+
)
179+
opts = SerializationOptions(strip_string_quotes=True)
180+
self.assertEqual(rule.serialize(opts), "prefix:${var.name}")
181+
162182

163183
# --- HeredocTemplateRule tests ---
164184

@@ -237,6 +257,18 @@ def test_serialize_no_preserve_invalid_raises(self):
237257
with self.assertRaises(RuntimeError):
238258
rule.serialize(opts)
239259

260+
def test_serialize_strip_string_quotes_preserve(self):
261+
token = HEREDOC_TEMPLATE("<<EOF\nhello world\nEOF")
262+
rule = HeredocTemplateRule([token])
263+
opts = SerializationOptions(preserve_heredocs=True, strip_string_quotes=True)
264+
self.assertEqual(rule.serialize(opts), "<<EOF\nhello world\nEOF")
265+
266+
def test_serialize_strip_string_quotes_no_preserve(self):
267+
token = HEREDOC_TEMPLATE("<<EOF\nline1\nline2\nEOF")
268+
rule = HeredocTemplateRule([token])
269+
opts = SerializationOptions(preserve_heredocs=False, strip_string_quotes=True)
270+
self.assertEqual(rule.serialize(opts), "line1\\nline2")
271+
240272

241273
# --- HeredocTrimTemplateRule tests ---
242274

@@ -310,3 +342,15 @@ def test_serialize_no_preserve_invalid_raises(self):
310342
opts = SerializationOptions(preserve_heredocs=False)
311343
with self.assertRaises(RuntimeError):
312344
rule.serialize(opts)
345+
346+
def test_serialize_strip_string_quotes_preserve(self):
347+
token = HEREDOC_TRIM_TEMPLATE("<<-EOF\n line1\n line2\nEOF")
348+
rule = HeredocTrimTemplateRule([token])
349+
opts = SerializationOptions(preserve_heredocs=True, strip_string_quotes=True)
350+
self.assertEqual(rule.serialize(opts), "<<-EOF\n line1\n line2\nEOF")
351+
352+
def test_serialize_strip_string_quotes_no_preserve(self):
353+
token = HEREDOC_TRIM_TEMPLATE("<<-EOF\n line1\n line2\nEOF")
354+
rule = HeredocTrimTemplateRule([token])
355+
opts = SerializationOptions(preserve_heredocs=False, strip_string_quotes=True)
356+
self.assertEqual(rule.serialize(opts), "line1\\nline2")

test/unit/test_api.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,26 @@ def test_block_parsing(self):
5959
result = loads(BLOCK_HCL)
6060
self.assertIn("resource", result)
6161

62+
def test_strip_string_quotes(self):
63+
result = loads(
64+
BLOCK_HCL,
65+
serialization_options=SerializationOptions(
66+
strip_string_quotes=True, explicit_blocks=False
67+
),
68+
)
69+
resource_list = result["resource"]
70+
self.assertEqual(len(resource_list), 1)
71+
block = resource_list[0]
72+
# Block label should have no surrounding quotes
73+
self.assertIn("aws_instance", block)
74+
inner = block["aws_instance"]
75+
self.assertIn("example", inner)
76+
body = inner["example"]
77+
# Attribute value should have no surrounding quotes
78+
self.assertEqual(body["ami"], "abc-123")
79+
# No __is_block__ marker
80+
self.assertNotIn("__is_block__", body)
81+
6282

6383
class TestLoad(TestCase):
6484
def test_from_file(self):

0 commit comments

Comments
 (0)