Skip to content

Commit 052defd

Browse files
authored
Merge pull request #167 from olehermanse/endofline
2 parents 6bb4e33 + 523c335 commit 052defd

7 files changed

Lines changed: 126 additions & 30 deletions

‎src/cfengine_cli/format.py‎

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
PROMISER_PARTS = {"promiser", "->", "stakeholder"}
3737

38+
SAMELINE_COMMENT_SEPARATOR = " "
39+
3840

3941
def _has_direct_macro(node: Node) -> bool:
4042
"""Check if any direct child of a node is a macro (non-recursive)."""
@@ -241,6 +243,9 @@ def split_generic_list(
241243
# outside the function call, so we do not add trailing commas into function
242244
# call argument lists.
243245
value_end_indices: list[int] = []
246+
# Same-line (trailing) comments are attached only after trailing commas are
247+
# placed, so the comma lands before the comment instead of after it.
248+
sameline_comments: dict[int, str] = {}
244249
for element in middle:
245250
if elements and element.type == ",":
246251
elements[-1] = elements[-1] + ","
@@ -249,7 +254,10 @@ def split_generic_list(
249254
elements.append(text(element))
250255
continue
251256
if element.type == "comment":
252-
elements.append(" " * indent + text(element))
257+
if _is_sameline_comment(element) and elements:
258+
sameline_comments[len(elements) - 1] = text(element)
259+
else:
260+
elements.append(" " * indent + text(element))
253261
continue
254262
line = " " * indent + stringify_single_line_node(element)
255263
# Strict < reserves 1 char for the comma appended after this check
@@ -275,6 +283,10 @@ def split_generic_list(
275283
if not elements[i].lstrip().startswith("#"):
276284
elements[i] = _set_trailing_comma(elements[i], trailing_comma)
277285
break
286+
287+
# Attach same-line comments to the end of their value lines
288+
for i, comment in sameline_comments.items():
289+
elements[i] = elements[i] + SAMELINE_COMMENT_SEPARATOR + comment
278290
return elements
279291

280292

@@ -354,7 +366,10 @@ def _format_attribute_with_macros(node: Node, indent: int) -> list[str]:
354366
if child.type == "macro":
355367
lines.append(text(child))
356368
elif child.type == "comment":
357-
lines.append(" " * (indent + 2) + text(child))
369+
if _is_sameline_comment(child) and lines:
370+
lines[-1] = lines[-1] + SAMELINE_COMMENT_SEPARATOR + text(child)
371+
else:
372+
lines.append(" " * (indent + 2) + text(child))
358373
else:
359374
lines.append(" " * (indent + 2) + stringify_single_line_node(child))
360375

@@ -505,13 +520,19 @@ def _format_stakeholder_elements(
505520
return maybe_split_generic_list(middle, indent, line_length)
506521
# Comments present — format element-by-element to preserve them
507522
elements: list[str] = []
523+
# Same-line (trailing) comments are attached only after the trailing comma is
524+
# placed, so the comma lands before the comment instead of after it.
525+
sameline_comments: dict[int, str] = {}
508526
for node in middle:
509527
if node.type == ",":
510528
if elements:
511529
elements[-1] = elements[-1] + ","
512530
continue
513531
if node.type == "comment":
514-
elements.append(" " * indent + text(node))
532+
if _is_sameline_comment(node) and elements:
533+
sameline_comments[len(elements) - 1] = text(node)
534+
else:
535+
elements.append(" " * indent + text(node))
515536
else:
516537
line = " " * indent + stringify_single_line_node(node)
517538
# Strict < reserves 1 char for the comma appended after this check
@@ -528,6 +549,10 @@ def _format_stakeholder_elements(
528549
if not elements[i].endswith(","):
529550
elements[i] = elements[i] + ","
530551
break
552+
553+
# Attach inline comments to the end of their value lines
554+
for i, comment in sameline_comments.items():
555+
elements[i] = elements[i] + SAMELINE_COMMENT_SEPARATOR + comment
531556
return elements
532557

533558

@@ -682,9 +707,13 @@ def _format_block_header(node: Node, fmt: Formatter) -> list[Node]:
682707
"""Format a block header line and return the body's children for further processing."""
683708
header_parts: list[str] = []
684709
header_comments: list[str] = []
710+
# Direct comment children of the block (excludes comments inside a
711+
# parameter list) — candidates for being kept on the header line.
712+
direct_comments: list[Node] = []
685713
for x in node.children[0:-1]:
686714
if x.type == "comment":
687715
header_comments.append(text(x))
716+
direct_comments.append(x)
688717
elif x.type == "parameter_list":
689718
parts: list[str] = []
690719
for p in x.children:
@@ -696,6 +725,16 @@ def _format_block_header(node: Node, fmt: Formatter) -> list[Node]:
696725
else:
697726
header_parts.append(text(x))
698727
line = " ".join(header_parts)
728+
# A lone same-line comment stays on the header line. When the header has
729+
# more than one comment, keep them together on their own lines below the
730+
# header instead of splitting one off onto the header line.
731+
if (
732+
len(header_comments) == 1
733+
and len(direct_comments) == 1
734+
and _is_sameline_comment(direct_comments[0])
735+
):
736+
line += SAMELINE_COMMENT_SEPARATOR + header_comments[0]
737+
header_comments = []
699738
if not fmt.empty:
700739
prev_sib = node.prev_named_sibling
701740
# Skip over preceding empty comments since they will be removed
@@ -725,6 +764,11 @@ def _format_block_header(node: Node, fmt: Formatter) -> list[Node]:
725764

726765
def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool:
727766
"""Check if a blank separator line should precede this child node."""
767+
# Inline (trailing) comments are appended to the preceding line, so they
768+
# must never be preceded by a blank separator line.
769+
if child.type == "comment" and _is_sameline_comment(child):
770+
return False
771+
728772
prev = child.prev_named_sibling
729773
# Empty comments preceding this child will be dropped — look past them
730774
# so we evaluate against the real prior content.
@@ -798,6 +842,21 @@ def _needs_blank_line_before(child: Node, indent: int, line_length: int) -> bool
798842
# ---------------------------------------------------------------------------
799843

800844

845+
def _is_sameline_comment(node: Node) -> bool:
846+
"""Check if a comment sits on the same source line as the element before it.
847+
848+
A trailing comment shares the row of the preceding sibling (which may be
849+
a leaf like ',' or ';', or a whole promise/attribute). Such comments are
850+
kept on the same output line as that element rather than moved to their
851+
own line."""
852+
if node.type != "comment":
853+
return False
854+
prev = node.prev_sibling
855+
if prev is None:
856+
return False
857+
return prev.end_point[0] == node.start_point[0]
858+
859+
801860
def _is_empty_comment(node: Node) -> bool:
802861
"""Check if a bare '#' comment should be dropped.
803862
@@ -934,7 +993,11 @@ def _autoformat(
934993
else:
935994
fmt.print_same_line(node)
936995
elif node.type == "comment":
937-
if not _is_empty_comment(node):
996+
if _is_empty_comment(node):
997+
pass
998+
elif _is_sameline_comment(node) and not fmt.empty:
999+
fmt.print_same_line(SAMELINE_COMMENT_SEPARATOR + text(node))
1000+
else:
9381001
fmt.print(node, _comment_indent(node, indent))
9391002
else:
9401003
fmt.print(node, indent)

‎tests/format/004_comments.expected.cf‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,14 @@ bundle agent win_services
5252
"autostart_services"
5353
slist => {
5454
"Alerter",
55-
"W32Time",
56-
# Windows Time
55+
"W32Time", # Windows Time
5756
};
5857

5958
some_class::
6059
"autostart_services"
6160
slist => {
62-
"MpsSvc",
63-
# Windows Firewall
64-
"W32Time",
65-
# Windows Time
61+
"MpsSvc", # Windows Firewall
62+
"W32Time", # Windows Time
6663
};
6764

6865
some_class::

‎tests/format/005_bundle_comments.expected.cf‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
bundle agent a
2-
# @brief Description of a
1+
bundle agent a # @brief Description of a
32
{
43
reports:
54
"Hello, world!";

‎tests/format/010_stakeholder.expected.cf‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,7 @@ bundle agent yoda_two
111111
{
112112
reports:
113113
"hi" -> {
114-
"yoda",
115-
# this is yoda of course
116-
"boba",
117-
# and yet another star wars character
114+
"yoda", # this is yoda of course
115+
"boba", # and yet another star wars character
118116
};
119117
}

‎tests/format/011_promises.expected.cf‎

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ body common control
4343
services_autorun,
4444
@(services_autorun.bundles),
4545
# Agent bundle
46-
cfe_internal_management,
47-
# See cfe_internal/CFE_cfengine.cf
46+
cfe_internal_management, # See cfe_internal/CFE_cfengine.cf
4847
mpf_main,
4948
@(cfengine_enterprise_hub_ha.management_bundles),
5049
@(def.bundlesequence_end),
@@ -125,13 +124,9 @@ body common control
125124
lastseenexpireafter => "$(def.control_common_lastseenexpireafter)";
126125

127126
control_common_tls_min_version_defined::
128-
tls_min_version => "$(default:def.control_common_tls_min_version)";
129-
130-
# See also: allowtlsversion in body server control
127+
tls_min_version => "$(default:def.control_common_tls_min_version)"; # See also: allowtlsversion in body server control
131128
control_common_tls_ciphers_defined::
132-
tls_ciphers => "$(default:def.control_common_tls_ciphers)";
133-
134-
# See also: allowciphers in body server control
129+
tls_ciphers => "$(default:def.control_common_tls_ciphers)"; # See also: allowciphers in body server control
135130
control_common_system_log_level_defined::
136131
system_log_level => "$(default:def.control_common_system_log_level)";
137132

@@ -500,14 +495,10 @@ bundle common services_autorun
500495
# automatically.
501496
"inputs" slist => {};
502497
"found_inputs" slist => {};
503-
"bundles" slist => { "services_autorun" };
504-
505-
# run self
498+
"bundles" slist => { "services_autorun" }; # run self
506499
services_autorun|services_autorun_inputs|services_autorun_bundles::
507500
"inputs" slist => { "$(sys.local_libdir)/autorun.cf" };
508-
"bundles" slist => { "autorun" };
509-
510-
# run loaded bundles
501+
"bundles" slist => { "autorun" }; # run loaded bundles
511502
reports:
512503
DEBUG|DEBUG_services_autorun::
513504
"DEBUG $(this.bundle): Services Autorun Disabled"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
bundle agent main
2+
{
3+
vars:
4+
"x" string => "y"; # trailing comment after promise
5+
"list"
6+
slist => {
7+
"a", # first element
8+
"b",
9+
"c", # third element
10+
};
11+
12+
"multi"
13+
if => "something", # condition comment
14+
string => "value"; # value comment
15+
"own_line"
16+
# this comment is on its own line
17+
string => "value";
18+
}
19+
20+
body package_method apt
21+
{
22+
package_changes => "bulk"; # bulk method
23+
# standalone comment
24+
package_list_command => "/usr/bin/dpkg -l";
25+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
bundle agent main
2+
{
3+
vars:
4+
"x" string => "y"; # trailing comment after promise
5+
"list" slist => {
6+
"a", # first element
7+
"b",
8+
"c", # third element
9+
};
10+
"multi"
11+
if => "something", # condition comment
12+
string => "value"; # value comment
13+
"own_line"
14+
# this comment is on its own line
15+
string => "value";
16+
}
17+
18+
body package_method apt
19+
{
20+
package_changes => "bulk"; # bulk method
21+
# standalone comment
22+
package_list_command => "/usr/bin/dpkg -l";
23+
}

0 commit comments

Comments
 (0)