python-hcl2 7.1.0__tar.gz → 7.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/CHANGELOG.md +13 -1
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/PKG-INFO +8 -13
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/README.md +7 -12
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/hcl2.lark +10 -8
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/reconstructor.py +34 -5
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/transformer.py +32 -19
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/version.py +2 -2
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/PKG-INFO +8 -13
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.codacy.yml +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.coveragerc +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/CODEOWNERS +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/ISSUE_TEMPLATE/hcl2-parsing-error.md +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/workflows/codeql-analysis.yml +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/workflows/pr_check.yml +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/workflows/publish.yml +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.gitignore +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.pre-commit-config.yaml +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.yamllint.yml +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/LICENSE +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/MANIFEST.in +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/bin/terraform_test +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/__init__.py +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/__main__.py +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/api.py +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/builder.py +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/const.py +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/parser.py +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/py.typed +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/mypy.ini +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/pylintrc +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/pyproject.toml +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/SOURCES.txt +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/dependency_links.txt +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/entry_points.txt +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/not-zip-safe +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/requires.txt +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/top_level.txt +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/reports/.gitignore +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/requirements.txt +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/setup.cfg +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/test-requirements.txt +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/tox.ini +0 -0
- {python_hcl2-7.1.0 → python_hcl2-7.2.0}/tree-to-hcl2-reconstruction.md +0 -0
|
@@ -7,7 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
7
7
|
|
|
8
8
|
## \[Unreleased\]
|
|
9
9
|
|
|
10
|
-
- Nothing
|
|
10
|
+
- Nothing Yet
|
|
11
|
+
|
|
12
|
+
## \[7.2.0\] - 2025-04-24
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Possibility to parse deeply nested interpolations (formerly a Limitation), Thanks again, @weaversam8 ([#223](https://github.com/amplify-education/python-hcl2/pull/223))
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Issue parsing ellipsis in a separate line within `for` expression ([#221](https://github.com/amplify-education/python-hcl2/pull/221))
|
|
21
|
+
- Issue parsing inline expression as an object key; **see Limitations in README.md** ([#222](https://github.com/amplify-education/python-hcl2/pull/222))
|
|
22
|
+
- Preserve literals of e-notation floats in parsing and reconstruction. Thanks, @eranor ([#226](https://github.com/amplify-education/python-hcl2/pull/226))
|
|
11
23
|
|
|
12
24
|
## \[7.1.0\] - 2025-04-10
|
|
13
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hcl2
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.2.0
|
|
4
4
|
Summary: A parser for HCL2
|
|
5
5
|
Author-email: Amplify Education <github@amplify.com>
|
|
6
6
|
License: MIT
|
|
@@ -114,20 +114,15 @@ We’ll try to answer any PR’s promptly.
|
|
|
114
114
|
|
|
115
115
|
## Limitations
|
|
116
116
|
|
|
117
|
-
###
|
|
117
|
+
### Using inline expression as an object key
|
|
118
118
|
|
|
119
|
-
-
|
|
119
|
+
- Object key can be an expression as long as it is wrapped in parentheses:
|
|
120
120
|
```terraform
|
|
121
121
|
locals {
|
|
122
|
-
foo = "
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
```terraform
|
|
128
|
-
locals {
|
|
129
|
-
foo = "foo"
|
|
130
|
-
foo_bar = "${local.foo}-bar"
|
|
131
|
-
name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar"
|
|
122
|
+
foo = "bar"
|
|
123
|
+
baz = {
|
|
124
|
+
(format("key_prefix_%s", local.foo)) : "value"
|
|
125
|
+
# format("key_prefix_%s", local.foo) : "value" this will fail
|
|
126
|
+
}
|
|
132
127
|
}
|
|
133
128
|
```
|
|
@@ -87,20 +87,15 @@ We’ll try to answer any PR’s promptly.
|
|
|
87
87
|
|
|
88
88
|
## Limitations
|
|
89
89
|
|
|
90
|
-
###
|
|
90
|
+
### Using inline expression as an object key
|
|
91
91
|
|
|
92
|
-
-
|
|
92
|
+
- Object key can be an expression as long as it is wrapped in parentheses:
|
|
93
93
|
```terraform
|
|
94
94
|
locals {
|
|
95
|
-
foo = "
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```terraform
|
|
101
|
-
locals {
|
|
102
|
-
foo = "foo"
|
|
103
|
-
foo_bar = "${local.foo}-bar"
|
|
104
|
-
name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar"
|
|
95
|
+
foo = "bar"
|
|
96
|
+
baz = {
|
|
97
|
+
(format("key_prefix_%s", local.foo)) : "value"
|
|
98
|
+
# format("key_prefix_%s", local.foo) : "value" this will fail
|
|
99
|
+
}
|
|
105
100
|
}
|
|
106
101
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
start : body
|
|
2
2
|
body : (new_line_or_comment? (attribute | block))* new_line_or_comment?
|
|
3
3
|
attribute : identifier EQ expression
|
|
4
|
-
block : identifier (identifier | STRING_LIT)* new_line_or_comment? "{" body "}"
|
|
4
|
+
block : identifier (identifier | STRING_LIT | string_with_interpolation)* new_line_or_comment? "{" body "}"
|
|
5
5
|
new_line_or_comment: ( NL_OR_COMMENT )+
|
|
6
6
|
NL_OR_COMMENT: /\n[ \t]*/ | /#.*\n/ | /\/\/.*\n/ | /\/\*(.|\n)*?(\*\/)/
|
|
7
7
|
|
|
@@ -45,6 +45,7 @@ expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR
|
|
|
45
45
|
| float_lit
|
|
46
46
|
| int_lit
|
|
47
47
|
| STRING_LIT
|
|
48
|
+
| string_with_interpolation
|
|
48
49
|
| tuple
|
|
49
50
|
| object
|
|
50
51
|
| function_call
|
|
@@ -59,10 +60,10 @@ expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR
|
|
|
59
60
|
| for_tuple_expr
|
|
60
61
|
| for_object_expr
|
|
61
62
|
|
|
62
|
-
STRING_LIT : "\""
|
|
63
|
-
STRING_CHARS : /(?:(?!\${)([^"\\]
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
STRING_LIT : "\"" STRING_CHARS? "\""
|
|
64
|
+
STRING_CHARS : /(?:(?!\${)([^"\\]|\\.|\$\$))+/ // any character except '"', including escaped $$
|
|
65
|
+
string_with_interpolation: "\"" (STRING_CHARS)* interpolation_maybe_nested (STRING_CHARS | interpolation_maybe_nested)* "\""
|
|
66
|
+
interpolation_maybe_nested: "${" expression "}"
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
int_lit : NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+
|
|
@@ -75,8 +76,9 @@ EQ : /[ \t]*=(?!=|>)/
|
|
|
75
76
|
|
|
76
77
|
tuple : "[" (new_line_or_comment* expression new_line_or_comment* ",")* (new_line_or_comment* expression)? new_line_or_comment* "]"
|
|
77
78
|
object : "{" new_line_or_comment? (new_line_or_comment* (object_elem | (object_elem COMMA)) new_line_or_comment*)* "}"
|
|
78
|
-
object_elem :
|
|
79
|
-
object_elem_key : float_lit | int_lit | identifier | STRING_LIT | object_elem_key_dot_accessor
|
|
79
|
+
object_elem : object_elem_key ( EQ | COLON ) expression
|
|
80
|
+
object_elem_key : float_lit | int_lit | identifier | STRING_LIT | object_elem_key_dot_accessor | object_elem_key_expression
|
|
81
|
+
object_elem_key_expression : LPAR expression RPAR
|
|
80
82
|
object_elem_key_dot_accessor : identifier (DOT identifier)+
|
|
81
83
|
|
|
82
84
|
heredoc_template : /<<(?P<heredoc>[a-zA-Z][a-zA-Z0-9._-]+)\n?(?:.|\n)*?\n\s*(?P=heredoc)\n/
|
|
@@ -98,7 +100,7 @@ full_splat : "[*]" (get_attr | index)*
|
|
|
98
100
|
|
|
99
101
|
FOR_OBJECT_ARROW : "=>"
|
|
100
102
|
!for_tuple_expr : "[" new_line_or_comment? for_intro new_line_or_comment? expression new_line_or_comment? for_cond? new_line_or_comment? "]"
|
|
101
|
-
!for_object_expr : "{" new_line_or_comment? for_intro new_line_or_comment? expression FOR_OBJECT_ARROW new_line_or_comment? expression "..."? new_line_or_comment? for_cond? new_line_or_comment? "}"
|
|
103
|
+
!for_object_expr : "{" new_line_or_comment? for_intro new_line_or_comment? expression FOR_OBJECT_ARROW new_line_or_comment? expression new_line_or_comment? "..."? new_line_or_comment? for_cond? new_line_or_comment? "}"
|
|
102
104
|
!for_intro : "for" new_line_or_comment? identifier ("," identifier new_line_or_comment?)? new_line_or_comment? "in" new_line_or_comment? expression new_line_or_comment? ":" new_line_or_comment?
|
|
103
105
|
!for_cond : "if" new_line_or_comment? expression
|
|
104
106
|
|
|
@@ -260,6 +260,7 @@ class HCLReconstructor(Reconstructor):
|
|
|
260
260
|
Terminal("STRING_LIT"),
|
|
261
261
|
Terminal("DECIMAL"),
|
|
262
262
|
Terminal("NAME"),
|
|
263
|
+
Terminal("NEGATIVE_DECIMAL"),
|
|
263
264
|
]:
|
|
264
265
|
return True
|
|
265
266
|
|
|
@@ -412,10 +413,7 @@ class HCLReverseTransformer:
|
|
|
412
413
|
def _is_block(self, value: Any) -> bool:
|
|
413
414
|
if isinstance(value, dict):
|
|
414
415
|
block_body = value
|
|
415
|
-
if (
|
|
416
|
-
START_LINE_KEY in block_body.keys()
|
|
417
|
-
or END_LINE_KEY in block_body.keys()
|
|
418
|
-
):
|
|
416
|
+
if START_LINE_KEY in block_body.keys() or END_LINE_KEY in block_body.keys():
|
|
419
417
|
return True
|
|
420
418
|
|
|
421
419
|
try:
|
|
@@ -520,7 +518,7 @@ class HCLReverseTransformer:
|
|
|
520
518
|
|
|
521
519
|
return Tree(Token("RULE", "body"), children)
|
|
522
520
|
|
|
523
|
-
# pylint: disable=too-many-branches, too-many-return-statements
|
|
521
|
+
# pylint: disable=too-many-branches, too-many-return-statements too-many-statements
|
|
524
522
|
def _transform_value_to_expr_term(self, value, level) -> Union[Token, Tree]:
|
|
525
523
|
"""Transforms a value from a dictionary into an "expr_term" (a value in HCL2)
|
|
526
524
|
|
|
@@ -611,6 +609,37 @@ class HCLReverseTransformer:
|
|
|
611
609
|
],
|
|
612
610
|
)
|
|
613
611
|
|
|
612
|
+
if isinstance(value, float):
|
|
613
|
+
value = str(value)
|
|
614
|
+
literal = []
|
|
615
|
+
|
|
616
|
+
if value[0] == "-":
|
|
617
|
+
# pop two first chars - minus and a digit
|
|
618
|
+
literal.append(Token("NEGATIVE_DECIMAL", value[:2]))
|
|
619
|
+
value = value[2:]
|
|
620
|
+
|
|
621
|
+
while value != "":
|
|
622
|
+
char = value[0]
|
|
623
|
+
|
|
624
|
+
if char == ".":
|
|
625
|
+
# current char marks beginning of decimal part: pop all remaining chars and end the loop
|
|
626
|
+
literal.append(Token("DOT", char))
|
|
627
|
+
literal.extend(Token("DECIMAL", char) for char in value[1:])
|
|
628
|
+
break
|
|
629
|
+
|
|
630
|
+
if char == "e":
|
|
631
|
+
# current char marks beginning of e-notation: pop all remaining chars and end the loop
|
|
632
|
+
literal.append(Token("EXP_MARK", value))
|
|
633
|
+
break
|
|
634
|
+
|
|
635
|
+
literal.append(Token("DECIMAL", char))
|
|
636
|
+
value = value[1:]
|
|
637
|
+
|
|
638
|
+
return Tree(
|
|
639
|
+
Token("RULE", "expr_term"),
|
|
640
|
+
[Tree(Token("RULE", "float_lit"), literal)],
|
|
641
|
+
)
|
|
642
|
+
|
|
614
643
|
# store strings as single literals
|
|
615
644
|
if isinstance(value, str):
|
|
616
645
|
# potentially unpack a complex syntax structure
|
|
@@ -44,7 +44,10 @@ class DictTransformer(Transformer):
|
|
|
44
44
|
super().__init__()
|
|
45
45
|
|
|
46
46
|
def float_lit(self, args: List) -> float:
|
|
47
|
-
|
|
47
|
+
value = "".join([self.to_tf_inline(arg) for arg in args])
|
|
48
|
+
if "e" in value:
|
|
49
|
+
return self.to_string_dollar(value)
|
|
50
|
+
return float(value)
|
|
48
51
|
|
|
49
52
|
def int_lit(self, args: List) -> int:
|
|
50
53
|
return int("".join([self.to_tf_inline(arg) for arg in args]))
|
|
@@ -98,14 +101,9 @@ class DictTransformer(Transformer):
|
|
|
98
101
|
def object_elem(self, args: List) -> Dict:
|
|
99
102
|
# This returns a dict with a single key/value pair to make it easier to merge these
|
|
100
103
|
# into a bigger dict that is returned by the "object" function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
key = self.to_string_dollar(key)
|
|
105
|
-
value = args[4]
|
|
106
|
-
else:
|
|
107
|
-
key = self.strip_quotes(str(args[0].children[0]))
|
|
108
|
-
value = args[2]
|
|
104
|
+
|
|
105
|
+
key = self.strip_quotes(str(args[0].children[0]))
|
|
106
|
+
value = args[2]
|
|
109
107
|
|
|
110
108
|
value = self.to_string_dollar(value)
|
|
111
109
|
return {key: value}
|
|
@@ -113,6 +111,9 @@ class DictTransformer(Transformer):
|
|
|
113
111
|
def object_elem_key_dot_accessor(self, args: List) -> str:
|
|
114
112
|
return "".join(args)
|
|
115
113
|
|
|
114
|
+
def object_elem_key_expression(self, args: List) -> str:
|
|
115
|
+
return self.to_string_dollar("".join(args))
|
|
116
|
+
|
|
116
117
|
def object(self, args: List) -> Dict:
|
|
117
118
|
args = self.strip_new_line_tokens(args)
|
|
118
119
|
result: Dict[str, Any] = {}
|
|
@@ -179,7 +180,9 @@ class DictTransformer(Transformer):
|
|
|
179
180
|
return f"{args[0]} ? {args[1]} : {args[2]}"
|
|
180
181
|
|
|
181
182
|
def binary_op(self, args: List) -> str:
|
|
182
|
-
return " ".join(
|
|
183
|
+
return " ".join(
|
|
184
|
+
[self.unwrap_string_dollar(self.to_tf_inline(arg)) for arg in args]
|
|
185
|
+
)
|
|
183
186
|
|
|
184
187
|
def unary_op(self, args: List) -> str:
|
|
185
188
|
args = self.process_nulls(args)
|
|
@@ -306,21 +309,31 @@ class DictTransformer(Transformer):
|
|
|
306
309
|
"""
|
|
307
310
|
return [arg for arg in args if arg != "\n" and arg is not Discard]
|
|
308
311
|
|
|
312
|
+
def is_string_dollar(self, value: str) -> bool:
|
|
313
|
+
if not isinstance(value, str):
|
|
314
|
+
return False
|
|
315
|
+
return value.startswith("${") and value.endswith("}")
|
|
316
|
+
|
|
309
317
|
def to_string_dollar(self, value: Any) -> Any:
|
|
310
318
|
"""Wrap a string in ${ and }"""
|
|
311
|
-
if isinstance(value, str):
|
|
319
|
+
if not isinstance(value, str):
|
|
320
|
+
return value
|
|
312
321
|
# if it's already wrapped, pass it unmodified
|
|
313
|
-
|
|
314
|
-
|
|
322
|
+
if self.is_string_dollar(value):
|
|
323
|
+
return value
|
|
315
324
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
325
|
+
if value.startswith('"') and value.endswith('"'):
|
|
326
|
+
value = str(value)[1:-1]
|
|
327
|
+
return self.process_escape_sequences(value)
|
|
328
|
+
|
|
329
|
+
if self.is_type_keyword(value):
|
|
330
|
+
return value
|
|
319
331
|
|
|
320
|
-
|
|
321
|
-
return value
|
|
332
|
+
return f"${{{value}}}"
|
|
322
333
|
|
|
323
|
-
|
|
334
|
+
def unwrap_string_dollar(self, value: str):
|
|
335
|
+
if self.is_string_dollar(value):
|
|
336
|
+
return value[2:-1]
|
|
324
337
|
return value
|
|
325
338
|
|
|
326
339
|
def strip_quotes(self, value: Any) -> Any:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hcl2
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.2.0
|
|
4
4
|
Summary: A parser for HCL2
|
|
5
5
|
Author-email: Amplify Education <github@amplify.com>
|
|
6
6
|
License: MIT
|
|
@@ -114,20 +114,15 @@ We’ll try to answer any PR’s promptly.
|
|
|
114
114
|
|
|
115
115
|
## Limitations
|
|
116
116
|
|
|
117
|
-
###
|
|
117
|
+
### Using inline expression as an object key
|
|
118
118
|
|
|
119
|
-
-
|
|
119
|
+
- Object key can be an expression as long as it is wrapped in parentheses:
|
|
120
120
|
```terraform
|
|
121
121
|
locals {
|
|
122
|
-
foo = "
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
```terraform
|
|
128
|
-
locals {
|
|
129
|
-
foo = "foo"
|
|
130
|
-
foo_bar = "${local.foo}-bar"
|
|
131
|
-
name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar"
|
|
122
|
+
foo = "bar"
|
|
123
|
+
baz = {
|
|
124
|
+
(format("key_prefix_%s", local.foo)) : "value"
|
|
125
|
+
# format("key_prefix_%s", local.foo) : "value" this will fail
|
|
126
|
+
}
|
|
132
127
|
}
|
|
133
128
|
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|