11import logging
2- import os
3- import subprocess
4- import tempfile
52from pathlib import Path
63
7- from collagraph .sfc .parser import CGXParser
8-
94from .template_formatter import format_template
5+ from .utils import get_script_range , parse_cgx_file , run_ruff_format
106
117logger = logging .getLogger (__name__ )
128
139
14- def format_script (path , script_node , source_lines , check ):
15- """
16- Returns formatted source and the original location of the node
10+ def format_script (script_node , source_lines ):
1711 """
18- start , end = script_node . location [ 0 ], script_node . end [ 0 ] - 1
12+ Format script section (CLI version with printing).
1913
14+ Returns formatted source and the original location of the node.
15+ """
16+ start , end = get_script_range (script_node )
2017 source = "" .join (source_lines [start :end ])
2118
22- ruff_command = ["ruff" , "format" ]
23- if check :
24- ruff_command .append ("--check" )
25-
26- # Write source to a temp file
27- with tempfile .TemporaryDirectory () as directory :
28- target_file = Path (directory ) / "source.py"
29- target_file .write_text (source )
30- ruff_command .append (str (target_file ))
31-
32- # Enable color output for ruff
33- # TODO: check whether we want to force color or not?
34- env = os .environ .copy ()
35- env ["CLICOLOR_FORCE" ] = "1"
36- # Then run ruff to format the temp file
37- output = subprocess .run (ruff_command , capture_output = True , text = True , env = env )
38- with target_file .open (mode = "r" , encoding = "utf-8" ) as fh :
39- formatted_source = fh .readlines ()
40-
41- stdout = output .stdout .replace (str (target_file ), str (path ))
42-
43- print (stdout , end = "" ) # noqa: T201
44- if check :
45- if output .returncode :
46- exit (output .returncode )
19+ # Format using ruff
20+ formatted_source = run_ruff_format (source , check = False )
4721
48- # Then return the contents
49- return formatted_source , (start , end )
22+ # Convert back to lines for consistency
23+ formatted_lines = formatted_source .splitlines (keepends = True )
24+
25+ return formatted_lines , (start , end )
5026
5127
5228def format_file (path , check = False , write = True ):
5329 """
5430 Format CGX files (the contents of the script tag) with ruff.
5531
56- Returns 0 if everything succeeded, or nothing changed.
57- Returns 1 when running check and something would change.
58- Returns list of lines when write is set to False instead
59- of an error code (used for the test-suite).
32+ Args:
33+ content: The CGX file content as a string
34+ uri: Optional URI for logging purposes
35+
36+ Returns:
37+ 0 if everything succeeded, or nothing changed.
38+ 1 when running check and something would change.
39+ list of lines when write is set to False instead
40+ of an error code (used for the test-suite).
6041 """
6142 path = Path (path )
6243 if path .suffix != ".cgx" :
6344 return
6445
65- parser = CGXParser ()
66- parser .feed (path .read_text ())
67-
68- with path .open (mode = "r" ) as fh :
69- lines = fh .readlines ()
46+ content = path .read_text (encoding = "utf-8" )
47+ parsed = parse_cgx_file (content )
7048
71- script_node = parser .root .child_with_tag ("script" )
72- template_nodes = [
73- node
74- for node in parser .root .children
75- if not hasattr (node , "tag" ) or node .tag != "script"
76- ]
49+ lines = content .splitlines (keepends = True )
7750
78- script_content , script_location = format_script (path , script_node , lines , check )
79- formatted_template_nodes = [
80- format_template (node , lines , parser = parser ) for node in template_nodes
81- ]
51+ script_content , script_location = format_script (parsed .script_node , lines )
52+ formatted_template_nodes = [format_template (node ) for node in parsed .template_nodes ]
8253
8354 changed_script = lines [script_location [0 ] : script_location [1 ]] != script_content
8455 changed_template = any (
@@ -91,8 +62,9 @@ def format_file(path, check=False, write=True):
9162 changed = changed_script or changed_template or needs_newline_at_end_of_file
9263 if check :
9364 if changed :
94- logger . warning (f"Would change : { path } " )
65+ print (f"Would reformat : { path } " ) # noqa: T201
9566 return 1
67+ print ("1 file already formatted" ) # noqa: T201
9668 return 0
9769
9870 formatted_parts = reversed (
@@ -111,10 +83,109 @@ def format_file(path, check=False, write=True):
11183 if needs_newline_at_end_of_file :
11284 lines .append ("\n " )
11385
86+ # Print status message based on changes
87+ if changed :
88+ print ("1 file reformatted" ) # noqa: T201
89+ else :
90+ print ("1 file left unchanged" ) # noqa: T201
91+
11492 if not write :
93+ # For testing, return the lines instead of writing
11594 return lines
11695
11796 if changed :
118- with path .open (mode = "w" ) as fh :
97+ with path .open (mode = "w" , encoding = "utf-8" ) as fh :
11998 fh .writelines (lines )
99+
120100 return 0
101+
102+
103+ def format_cgx_content (content : str , uri : str = "" ) -> str :
104+ """
105+ Format CGX file content (for LSP use).
106+
107+ Args:
108+ content: The CGX file content as a string
109+ uri: Optional URI for logging purposes
110+
111+ Returns:
112+ Formatted content as a string
113+ """
114+ try :
115+ # Parse the CGX file
116+ parsed = parse_cgx_file (content )
117+
118+ # Split content into lines
119+ lines = content .splitlines (keepends = True )
120+
121+ # Check for script node
122+ if not parsed .script_node :
123+ logger .warning (f"Missing script node in { uri } " )
124+ return content
125+
126+ # Format script section (using updated signature for LSP)
127+ script_content , script_location = format_script_content (
128+ parsed .script_node , lines
129+ )
130+
131+ # Format all template nodes
132+ formatted_template_nodes = [
133+ format_template (node ) for node in parsed .template_nodes
134+ ]
135+
136+ # Check if newline is needed at end of file
137+ needs_newline_at_end_of_file = lines and not lines [- 1 ].endswith ("\n " )
138+
139+ # Sort all formatted parts by their starting location
140+ # (in reverse order for replacement)
141+ formatted_parts = reversed (
142+ sorted (
143+ [
144+ (script_content , script_location ),
145+ * formatted_template_nodes ,
146+ ],
147+ key = lambda x : x [1 ][0 ],
148+ )
149+ )
150+
151+ # Replace content in reverse order to maintain correct line indices
152+ for formatted_content , (start , end ) in formatted_parts :
153+ lines [start :end ] = formatted_content
154+
155+ # Add newline at end if needed
156+ if needs_newline_at_end_of_file :
157+ lines .append ("\n " )
158+
159+ return "" .join (lines )
160+
161+ except Exception as e :
162+ logger .error (f"Error formatting { uri } : { e } " , exc_info = True )
163+ # Return original content on error
164+ return content
165+
166+
167+ def format_script_content (script_node , source_lines ):
168+ """
169+ Format the script section of a CGX file using ruff (for LSP use).
170+
171+ Args:
172+ script_node: script node from collagraph's parser
173+ source_lines: contents of source file as list of lines
174+
175+ Returns:
176+ Formatted source and the original location of the node.
177+ """
178+ if script_node .end is None :
179+ raise RuntimeError ("Invalid script node: no end" )
180+
181+ start , end = get_script_range (script_node )
182+ source = "" .join (source_lines [start :end ])
183+
184+ # Format using ruff
185+ formatted_source = run_ruff_format (source )
186+
187+ # Convert to lines for consistency
188+ formatted_lines = formatted_source .splitlines (keepends = True )
189+
190+ # Return the formatted content and its location
191+ return formatted_lines , (start , end )
0 commit comments