From 90cf340579f0733b9f7d0dd15ef4662c08561f74 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Tue, 31 Mar 2026 02:30:53 +0200 Subject: [PATCH 1/3] Re-enabled formatting tests Signed-off-by: Ole Herman Schumacher Elgesem --- tests/run-format-tests.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/run-format-tests.sh b/tests/run-format-tests.sh index c57d40c..150af46 100644 --- a/tests/run-format-tests.sh +++ b/tests/run-format-tests.sh @@ -1,8 +1,6 @@ #/usr/bin/env bash -echo "TODO: Uncomment set -e to make tests start failing" - -# set -e +set -e # set -x echo "These tests expect cfengine CLI to be installed globally or in venv" From f6ca7f58d1873391a1aab624862b1dbdb5d0f594 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Tue, 31 Mar 2026 02:31:29 +0200 Subject: [PATCH 2/3] Made formatting test expectations a bit more consistent Signed-off-by: Ole Herman Schumacher Elgesem --- tests/format/002_basics.expected.cf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/format/002_basics.expected.cf b/tests/format/002_basics.expected.cf index e662300..64a236f 100644 --- a/tests/format/002_basics.expected.cf +++ b/tests/format/002_basics.expected.cf @@ -1,6 +1,5 @@ # This shows some basics of policy language, without getting # too deep into edge cases and wrappting etc. - body common control { inputs => {"/var/cfengine/inputs/some_file.cf"}; @@ -24,6 +23,7 @@ bundle agent main "foo" if => "bar" string => "some_value"; + baz:: "bam" if => "bar" From ab916668940555efaf0ce196e45245d42aeda100 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Tue, 31 Mar 2026 01:44:28 +0200 Subject: [PATCH 3/3] Implemented formatting according to the tests Co-authored-by: Claude Opus 4.6 (1M context) Tickets: ENT-13076, ENT-13113 Signed-off-by: Ole Herman Schumacher Elgesem --- src/cfengine_cli/format.py | 101 +++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/src/cfengine_cli/format.py b/src/cfengine_cli/format.py index ee9a672..8595fc1 100644 --- a/src/cfengine_cli/format.py +++ b/src/cfengine_cli/format.py @@ -60,10 +60,6 @@ def stringify_children(children): result += " " if previous and previous.type == "=>": result += " " - if previous and previous.type == "{": - result += " " - if previous and child.type == "}": - result += " " result += string previous = child return result @@ -75,8 +71,11 @@ def stringify_single_line(node): return stringify_children(node.children) -def split_generic_value(node, indent): - # TODO +def split_generic_value(node, indent, line_length): + if node.type == "call": + return split_rval_call(node, indent, line_length) + if node.type == "list": + return split_rval_list(node, indent, line_length) return [stringify_single_line(node)] @@ -90,7 +89,7 @@ def split_generic_list(middle, indent, line_length): if len(line) < line_length: elements.append(line) else: - lines = split_generic_value(element, indent) + lines = split_generic_value(element, indent, line_length) elements.append(" " * indent + lines[0]) elements.extend(lines[1:]) return elements @@ -151,15 +150,17 @@ def attempt_split_attribute(node, indent, line_length): lines = maybe_split_rval(rval, indent, offset, line_length) lines[0] = prefix + lines[0] return lines - return [stringify_single_line(node)] + return [" " * indent + stringify_single_line(node)] def stringify(node, indent, line_length): single_line = " " * indent + stringify_single_line(node) - if len(single_line) < line_length: + # Reserve 1 char for trailing ; or , after attributes + effective_length = line_length - 1 if node.type == "attribute" else line_length + if len(single_line) < effective_length: return [single_line] if node.type == "attribute": - return attempt_split_attribute(node, indent, line_length) + return attempt_split_attribute(node, indent, line_length - 1) return [single_line] @@ -178,14 +179,18 @@ def autoformat(node, fmt, line_length, macro_indent, indent=0): if node.type in ["bundle_block", "promise_block", "body_block"]: line = " ".join(x.text.decode("utf-8") for x in node.children[0:-1]) if not fmt.empty: - fmt.print("", 0) + prev_sib = node.prev_named_sibling + if not (prev_sib and prev_sib.type == "comment"): + fmt.print("", 0) fmt.print(line, 0) children = node.children[-1].children if node.type in [ "bundle_section", "class_guarded_promises", "class_guarded_body_attributes", + "class_guarded_promise_block_attributes", "promise", + "half_promise", "attribute", ]: indent += 2 @@ -193,13 +198,87 @@ def autoformat(node, fmt, line_length, macro_indent, indent=0): lines = stringify(node, indent, line_length) fmt.print_lines(lines, indent=0) return + if node.type == "promise": + # Single-line promise: if exactly 1 attribute, no half_promise continuation, + # and the whole line fits in line_length + attr_children = [c for c in children if c.type == "attribute"] + next_sib = node.next_named_sibling + has_continuation = next_sib and next_sib.type == "half_promise" + if len(attr_children) == 1 and not has_continuation: + promiser_node = next((c for c in children if c.type == "promiser"), None) + if promiser_node: + line = ( + text(promiser_node) + + " " + + stringify_single_line(attr_children[0]) + + ";" + ) + if indent + len(line) <= line_length: + fmt.print(line, indent) + return if children: for child in children: + # Blank line between promises in a section + if child.type == "promise": + prev = child.prev_named_sibling + if prev and prev.type in ["promise", "half_promise"]: + fmt.print("", 0) + elif child.type in [ + "class_guarded_promises", + "class_guarded_body_attributes", + "class_guarded_promise_block_attributes", + ]: + prev = child.prev_named_sibling + if prev and prev.type in [ + "promise", + "half_promise", + ]: + fmt.print("", 0) + elif child.type == "comment": + prev = child.prev_named_sibling + if prev and prev.type in ["promise", "half_promise"]: + parent = child.parent + if parent and parent.type in [ + "bundle_section", + "class_guarded_promises", + ]: + fmt.print("", 0) autoformat(child, fmt, line_length, macro_indent, indent) return if node.type in [",", ";"]: fmt.print_same_line(node) return + if node.type == "comment": + comment_indent = indent + next_sib = node.next_named_sibling + while next_sib and next_sib.type == "comment": + next_sib = next_sib.next_named_sibling + if next_sib is None: + prev_sib = node.prev_named_sibling + while prev_sib and prev_sib.type == "comment": + prev_sib = prev_sib.prev_named_sibling + if prev_sib and prev_sib.type in [ + "bundle_section", + "class_guarded_promises", + "class_guarded_body_attributes", + "class_guarded_promise_block_attributes", + "promise", + "half_promise", + "attribute", + ]: + comment_indent = indent + 2 + elif next_sib.type in [ + "bundle_section", + "class_guarded_promises", + "class_guarded_body_attributes", + "class_guarded_promise_block_attributes", + "promise", + "half_promise", + "attribute", + ]: + comment_indent = indent + 2 + fmt.print(node, comment_indent) + return fmt.print(node, indent)