@@ -397,7 +397,7 @@ def show_file_lines(file_path: Path, num_lines: int = 10) -> str:
397
397
398
398
399
399
def process_file (file_path : Path , mode : str , authors : str , show_diff : bool = False , debug : bool = False ,
400
- require_shebang : Optional [bool ] = None , require_encoding : bool = True ) -> Optional [Dict [str , Any ]]:
400
+ require_shebang : Optional [bool ] = None , require_encoding : bool = True ) -> Optional [Dict [str , Any ]]:
401
401
"""Check a single file and optionally fix its header.
402
402
403
403
Args:
@@ -476,6 +476,8 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
476
476
location_match = re .search (r"^Location: \./(.*)$" , docstring_node , re .MULTILINE )
477
477
if not location_match :
478
478
issues .append ("Missing 'Location' line" )
479
+ elif location_match .group (1 ) != relative_path_str :
480
+ issues .append (f"Incorrect 'Location' line: expected './{ relative_path_str } ', found './{ location_match .group (1 )} '" )
479
481
480
482
if f"Copyright { COPYRIGHT_YEAR } " not in docstring_node :
481
483
issues .append ("Missing 'Copyright' line" )
@@ -489,7 +491,8 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
489
491
if not issues :
490
492
return None
491
493
492
- if mode in ["fix-all" , "fix" , "interactive" ]:
494
+ # Generate new source code for diff preview or actual fixing
495
+ if mode in ["fix-all" , "fix" , "interactive" ] or show_diff :
493
496
# Extract the raw docstring from source
494
497
if module_body and isinstance (module_body [0 ], ast .Expr ):
495
498
docstring_expr_node = module_body [0 ]
@@ -500,38 +503,68 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
500
503
quotes = '"""' if raw_docstring .startswith ('"""' ) else "'''"
501
504
inner_content = raw_docstring .strip (quotes )
502
505
503
- # Extract existing header fields and body
506
+ # Extract existing header fields
504
507
existing_header_fields = extract_header_info (source_code , inner_content )
505
508
506
- # Find where the header ends and body begins
509
+ # Split docstring into lines for analysis
507
510
docstring_lines = inner_content .strip ().splitlines ()
508
- header_end_idx = 0
511
+
512
+ # Separate the docstring into header and content parts
513
+ content_lines = []
514
+ in_header_section = False
509
515
510
516
for i , line in enumerate (docstring_lines ):
511
- if any (line .strip ().startswith (field + ":" ) for field in HEADER_FIELDS ):
512
- header_end_idx = i + 1
513
- elif header_end_idx > 0 and line .strip ():
514
- # Found first non-header content
517
+ line_stripped = line .strip ()
518
+
519
+ # Check if this line is a header field
520
+ is_header_field = (any (line_stripped .startswith (field + ":" ) for field in HEADER_FIELDS ) or
521
+ line_stripped .startswith ("Copyright" ))
522
+
523
+ if is_header_field :
524
+ in_header_section = True
525
+ elif in_header_section and not line_stripped :
526
+ # Empty line might separate header from content - continue checking
527
+ continue
528
+ elif in_header_section and line_stripped and not is_header_field :
529
+ # Found content after header section - this and everything after is content
530
+ content_lines .extend (docstring_lines [i :])
515
531
break
516
-
517
- # Extract body content
518
- docstring_body_lines = docstring_lines [header_end_idx :]
519
- if docstring_body_lines and not docstring_body_lines [0 ].strip ():
520
- docstring_body_lines = docstring_body_lines [1 :]
532
+ elif not in_header_section and line_stripped :
533
+ # Content before any header section (like module descriptions)
534
+ # Look ahead to see if there are headers following
535
+ has_headers_following = any (
536
+ any (future_line .strip ().startswith (field + ":" ) for field in HEADER_FIELDS ) or
537
+ future_line .strip ().startswith ("Copyright" )
538
+ for future_line in docstring_lines [i + 1 :]
539
+ )
540
+ if has_headers_following :
541
+ # This is content, headers follow later
542
+ content_lines .append (line )
543
+ else :
544
+ # No headers following, this is regular content
545
+ content_lines .extend (docstring_lines [i :])
546
+ break
521
547
522
548
# Build new header
523
549
new_header_lines = []
524
- new_header_lines .append (existing_header_fields .get ("Location" ) or f"Location: ./{ relative_path_str } " )
550
+ # Always use correct location path
551
+ new_header_lines .append (f"Location: ./{ relative_path_str } " )
525
552
new_header_lines .append (existing_header_fields .get ("Copyright" ) or f"Copyright { COPYRIGHT_YEAR } " )
526
553
new_header_lines .append (existing_header_fields .get ("SPDX-License-Identifier" ) or f"SPDX-License-Identifier: { LICENSE } " )
527
- new_header_lines .append (f"Authors: { authors } " )
554
+ # Preserve existing Authors field if it exists, otherwise use the provided authors
555
+ new_header_lines .append (existing_header_fields .get ("Authors" ) or f"Authors: { authors } " )
528
556
529
- # Reconstruct docstring
557
+ # Reconstruct docstring with preserved content
530
558
new_inner_content = "\n " .join (new_header_lines )
531
- if docstring_body_lines :
532
- new_inner_content += "\n \n " + "\n " .join (docstring_body_lines ).strip ()
559
+ if content_lines :
560
+ content_str = "\n " .join (content_lines )
561
+ new_inner_content += "\n \n " + content_str
562
+
563
+ # Ensure proper ending with newline before closing quotes
564
+ if not new_inner_content .endswith ('\n ' ):
565
+ new_inner_content += '\n '
533
566
534
- new_docstring = f"{ quotes } { new_inner_content . strip () } { quotes } "
567
+ new_docstring = f"{ quotes } { new_inner_content } { quotes } "
535
568
536
569
# Prepare source with appropriate headers
537
570
header_lines = []
@@ -562,7 +595,8 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
562
595
# No docstring found
563
596
issues .append ("No docstring found" )
564
597
565
- if mode in ["fix-all" , "fix" , "interactive" ]:
598
+ # Generate new source code for diff preview or actual fixing
599
+ if mode in ["fix-all" , "fix" , "interactive" ] or show_diff :
566
600
# Create new header
567
601
new_header = get_header_template (
568
602
relative_path_str ,
@@ -683,15 +717,15 @@ def parse_arguments(argv: Optional[List[str]] = None) -> argparse.Namespace:
683
717
# Header configuration options
684
718
header_group = parser .add_argument_group ("header configuration" )
685
719
header_group .add_argument ("--require-shebang" , choices = ["always" , "never" , "auto" ], default = "auto" ,
686
- help = "Require shebang line: 'always', 'never', or 'auto' (only for executable files). Default: auto" )
720
+ help = "Require shebang line: 'always', 'never', or 'auto' (only for executable files). Default: auto" )
687
721
header_group .add_argument ("--require-encoding" , action = "store_true" , default = True ,
688
- help = "Require encoding line. Default: True" )
722
+ help = "Require encoding line. Default: True" )
689
723
header_group .add_argument ("--no-encoding" , action = "store_false" , dest = "require_encoding" ,
690
- help = "Don't require encoding line." )
724
+ help = "Don't require encoding line." )
691
725
header_group .add_argument ("--copyright-year" , type = int , default = COPYRIGHT_YEAR ,
692
- help = f"Copyright year to use. Default: { COPYRIGHT_YEAR } " )
726
+ help = f"Copyright year to use. Default: { COPYRIGHT_YEAR } " )
693
727
header_group .add_argument ("--license" , type = str , default = LICENSE ,
694
- help = f"License identifier to use. Default: { LICENSE } " )
728
+ help = f"License identifier to use. Default: { LICENSE } " )
695
729
696
730
return parser .parse_args (argv )
697
731
@@ -830,7 +864,7 @@ def print_results(issues_found: List[Dict[str, Any]], mode: str, modified_count:
830
864
# Show debug info if available
831
865
if "debug" in issue_info :
832
866
debug = issue_info ["debug" ]
833
- print (f " Debug info:" , file = sys .stderr )
867
+ print (" Debug info:" , file = sys .stderr )
834
868
print (f" Executable: { debug ['executable' ]} " , file = sys .stderr )
835
869
print (f" Has shebang: { debug ['has_shebang' ]} " , file = sys .stderr )
836
870
print (f" Has encoding: { debug ['has_encoding' ]} " , file = sys .stderr )
0 commit comments