Skip to content

Commit ff33091

Browse files
EliEli
authored andcommitted
Added run_nday to param.nml as a property. Also neatened the writer to preserve blank lines preceding comments.
1 parent 5c84f97 commit ff33091

3 files changed

Lines changed: 852 additions & 21 deletions

File tree

schimpy/nml.py

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5298
def 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

schimpy/param.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,25 @@ def set_run_start(self, run_start):
122122

123123
run_start = property(get_run_start, set_run_start)
124124

125+
126+
# NEW: getter/setter for total run length (days), stored in CORE::rnday
127+
def get_run_nday(self):
128+
"""
129+
Return total run length (in days) as an int, based on CORE::rnday.
130+
"""
131+
core = self._require_section_keys("CORE", ("rnday",))
132+
return int(core["rnday"]["value"])
133+
134+
def set_run_nday(self, nday):
135+
"""
136+
Set total run length (in days), writing CORE::rnday as an int.
137+
"""
138+
core = self._require_section_keys("CORE", ("rnday",))
139+
core["rnday"]["value"] = int(nday)
140+
141+
run_nday = property(get_run_nday, set_run_nday)
142+
143+
125144
def set_interval(self, name, freq):
126145
"""Set binary output frequency using Pandas offset or string that evaluates as offset"""
127146
dt = int(self["dt"])

0 commit comments

Comments
 (0)