@@ -4,76 +4,145 @@ def parse(file_content):
44 If there's a line starting with !, the parser will store it as the full_line_comment and attach it to the next key-value pair it encounters.
55 If there's an inline comment starting with !, it will be stored as inline_comment.
66
7- For the most part this parser is comment preserving; you might notice some whitespace and blank line(s) being eliminated in the round trip through write method below
7+ For the most part this parser is comment preserving; you might notice some whitespace and blank line(s) being eliminated in the round trip through write method below.
8+ NOTE: If a comment block is preceded by a blank line, this fact is now preserved and used when writing back out.
89 """
910 namelists = {}
1011 current_namelist = None
12+
1113 full_line_comment = ""
12- for line in file_content .splitlines ():
13- line = line .strip ()
14- if line .startswith ("!" ):
14+ full_line_comment_leading_blank = False # NEW: did a blank line precede this comment block?
15+ prev_blank = False # NEW: was the previous raw line blank?
16+
17+ for raw_line in file_content .splitlines ():
18+ stripped = raw_line .strip ()
19+
20+ # Track blanks explicitly
21+ if stripped == "" :
22+ prev_blank = True
23+ continue
24+
25+ if stripped .startswith ("!" ):
26+ # Full-line comment, possibly spanning multiple lines
1527 if len (full_line_comment ) == 0 :
16- full_line_comment = line [1 :]
28+ full_line_comment = stripped [1 :]
29+ full_line_comment_leading_blank = prev_blank
1730 else :
18- full_line_comment = full_line_comment + "\n " + line [1 :]
19- elif line .startswith ("&" ):
20- current_namelist = line [1 :].strip ()
21- namelists [current_namelist ] = {"comment" : full_line_comment }
31+ full_line_comment = full_line_comment + "\n " + stripped [1 :]
32+ # keep whatever leading_blank state we already had
33+ prev_blank = False
34+
35+ elif stripped .startswith ("&" ):
36+ current_namelist = stripped [1 :].strip ()
37+ namelists [current_namelist ] = {
38+ "comment" : full_line_comment ,
39+ }
40+ # NEW: remember if the namelist comment was preceded by a blank line
41+ if len (full_line_comment ) != 0 :
42+ namelists [current_namelist ]["comment_leading_blank" ] = full_line_comment_leading_blank
2243 full_line_comment = ""
23- elif line .startswith ("/" ):
44+ full_line_comment_leading_blank = False
45+ prev_blank = False
46+
47+ elif stripped .startswith ("/" ):
2448 current_namelist = None
49+ full_line_comment = ""
50+ full_line_comment_leading_blank = False
51+ prev_blank = False
52+
2553 elif current_namelist is not None :
26- line_parts = line .split ("!" , maxsplit = 1 )
54+ # Key/value line (possibly with inline comment)
55+ line_parts = stripped .split ("!" , maxsplit = 1 )
2756 key_value_pair = line_parts [0 ].strip ()
2857 inline_comment = line_parts [1 ] if len (line_parts ) == 2 else ""
2958 if not key_value_pair :
59+ prev_blank = False
3060 continue
61+
3162 key , value = key_value_pair .split ("=" )
3263 key = key .strip ()
3364 value = value .strip ()
3465
3566 try :
3667 value = int (value )
37- except :
68+ except Exception :
3869 try :
3970 value = float (value )
40- except :
71+ except Exception :
4172 pass
4273
43- namelists [ current_namelist ][ key ] = {
74+ entry = {
4475 "value" : value ,
4576 "full_line_comment" : full_line_comment ,
4677 "inline_comment" : inline_comment ,
4778 }
79+ # NEW: remember if the key's full-line comment was preceded by a blank line
80+ if len (full_line_comment ) != 0 :
81+ entry ["full_line_comment_leading_blank" ] = full_line_comment_leading_blank
82+
83+ namelists [current_namelist ][key ] = entry
84+
85+ full_line_comment = ""
86+ full_line_comment_leading_blank = False
87+ prev_blank = False
88+
89+ else :
90+ # Outside any namelist and not a comment; just reset flags
4891 full_line_comment = ""
92+ full_line_comment_leading_blank = False
93+ prev_blank = False
94+
4995 return namelists
5096
5197
5298def write (namelist_data ):
5399 """
54- writes out the namelist dictionary from the parse method to a string. The comments are preserved but not the whitespace or indentations
100+ writes out the namelist dictionary from the parse method to a string. The comments are preserved but not the whitespace or indentations.
101+
102+ If a comment block (either namelist-level or per-key full-line comment) was preceded by a blank line in the original file,
103+ a single blank line is written before that comment block.
55104 """
56105 lines = []
57106 for namelist_name , namelist_values in namelist_data .items ():
58107 comment = namelist_values .get ("comment" )
108+ comment_leading_blank = namelist_values .get ("comment_leading_blank" , False )
109+
110+ # Namelist-level comment (possibly multi-line)
59111 if comment and len (comment ) != 0 :
112+ if comment_leading_blank :
113+ lines .append ("" ) # restore the blank line before the comment
60114 lines .append ("!{}" .format (comment .replace ("\n " , "\n !" )))
115+
61116 lines .append ("&{}" .format (namelist_name ))
117+
62118 for key , value_data in namelist_values .items ():
63- if key == "comment" : # already taken care of above
64- continue
65- if "full_line_comment" in value_data : # do this first
66- full_line_comment = value_data .get ("full_line_comment" )
119+ if key in ("comment" , "comment_leading_blank" ):
120+ continue # handled above
121+
122+ # Handle per-key full-line comment
123+ if isinstance (value_data , dict ) and "full_line_comment" in value_data :
124+ full_line_comment = value_data .get ("full_line_comment" , "" )
67125 if len (full_line_comment ) != 0 :
126+ leading_blank = value_data .get ("full_line_comment_leading_blank" , False )
127+ if leading_blank :
128+ lines .append ("" ) # restore blank before the comment
68129 lines .append ("!{}" .format (full_line_comment .replace ("\n " , "\n !" )))
69- if key == "full_line_comment" :
70- continue # took care of it above
130+
131+ if key in ("full_line_comment" , "full_line_comment_leading_blank" ):
132+ continue # internal metadata, not an actual namelist key
133+
134+ if not isinstance (value_data , dict ) or "value" not in value_data :
135+ continue # skip any non-standard entries
136+
137+ # Key/value line
71138 lines .append (" {} = {}" .format (key , value_data ["value" ]))
139+
72140 inline_comment = value_data .get ("inline_comment" )
73141 if inline_comment and len (inline_comment ) != 0 :
74142 lines [- 1 ] = lines [- 1 ] + (
75143 " !{}" .format (inline_comment .replace ("\n " , "" ))
76144 )
145+
77146 lines .append ("/" )
78147 return "\n " .join (lines )
79148
0 commit comments