Skip to content

Commit a666ed4

Browse files
authored
ci: update rules in fortran style script (MODFLOW-ORG#1651)
* add rules: no trailing return statements, clean up comments (no double dashes, no empty comments, internal comment spacing) * run black/isort on scripts in .github/common/
1 parent 9af21a0 commit a666ed4

File tree

3 files changed

+132
-95
lines changed

3 files changed

+132
-95
lines changed

.github/common/fortran_format_check.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import os
2-
import sys
31
import argparse
42
import glob
3+
import os
4+
import sys
55
from pathlib import Path
66
from subprocess import run
77

Lines changed: 128 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,159 @@
11
import argparse
2-
import re
3-
from contextlib import nullcontext
2+
import string
43
from itertools import repeat
54
from pathlib import Path
6-
from typing import Iterator, Optional
7-
from warnings import warn
85

96
from fprettify.fparse_utils import InputStream
107

11-
INTENT_PATTERN = re.compile(r".*(intent\(.+\)).*")
8+
ALPHANUMERICS = set(string.ascii_letters + string.digits)
129

1310

14-
def get_intent(s) -> Optional[str]:
15-
result = INTENT_PATTERN.match(s)
16-
return result.group(1) if result else None
11+
def join_comments(comments) -> str:
12+
return "".join([c for c in comments if any(c)])
1713

1814

19-
def get_param(s) -> bool:
20-
return "parameter" in s
21-
22-
23-
def get_comments(comments) -> Iterator[str]:
24-
for comment in comments:
25-
if not any(comment):
26-
continue
27-
yield comment.rstrip()
28-
29-
30-
class Transforms:
15+
class Rules:
3116
@staticmethod
32-
def separate_lines(path, overwrite=False):
17+
def separate_lines(path):
3318
"""Variables defined on separate lines"""
3419

3520
flines = []
3621
with open(path, "r") as f:
3722
stream = InputStream(f)
3823
while 1:
39-
line, comments, lines = stream.next_fortran_line()
24+
line, comment, lines = stream.next_fortran_line()
4025
if not lines:
4126
break
4227
line = line.rstrip()
4328
parts = line.rpartition("::")
44-
comments = " " + "".join(get_comments(comments))
45-
if not parts[1] or "procedure" in parts[0]:
46-
for l in lines:
47-
flines.append(l.rstrip())
29+
comment = join_comments(comment)
30+
31+
if (
32+
not parts[1]
33+
or "procedure" in parts[0]
34+
or parts[0].strip().startswith("use")
35+
):
36+
flines.extend(lines)
4837
continue
4938

50-
nspaces = len(lines[0]) - len(lines[0].lstrip())
51-
prefix = "".join(repeat(" ", nspaces))
52-
vtype = parts[0].split(",")[0].strip()
53-
split = parts[2].split(",")
54-
intent = get_intent(parts[0])
55-
param = get_param(parts[0])
39+
indent = "".join(repeat(" ", len(lines[0]) - len(lines[0].lstrip())))
40+
quals = [q.strip() for q in parts[0].split(",")] # qualifiers
41+
vars = [v.strip() for v in parts[2].split(",")] # variable names
5642

5743
if not line:
5844
continue
59-
if (len(parts[0]) == 0 and len(parts[1]) == 0) or (
60-
"(" in parts[2] or ")" in parts[2]
45+
if (
46+
(len(parts[0]) == 0 and len(parts[1]) == 0)
47+
or ("(" in parts[2] or ")" in parts[2])
48+
or len(vars) == 1
49+
or "parameter" in parts[0]
6150
):
62-
flines.append(prefix + line + comments)
63-
elif len(split) == 1:
64-
flines.append(prefix + line + comments)
65-
elif param:
66-
flines.append(prefix + line + comments)
51+
flines.extend(lines)
6752
else:
68-
for s in split:
69-
if s.strip() == "&":
53+
for s in vars:
54+
if s == "&":
7055
continue
71-
l = prefix + vtype
72-
if intent:
73-
l += f", {intent}"
74-
l += f" :: {s.strip()}"
75-
flines.append(l + comments)
76-
77-
with open(path, "w") if overwrite else nullcontext() as f:
78-
79-
def write(line):
80-
if overwrite:
81-
f.write(line + "\n")
82-
else:
83-
print(line)
56+
l = indent + ", ".join(quals)
57+
l += f" :: {s}"
58+
flines.append(l + comment)
8459

60+
with open(path, "w") as f:
8561
for line in flines:
86-
write(line)
62+
f.write(line.rstrip() + "\n")
8763

8864
@staticmethod
89-
def no_return_statements(path, overwrite=False):
65+
def trailing_returns(path):
9066
"""Remove return statements at the end of routines"""
91-
# todo
92-
pass
67+
68+
flines = []
69+
with open(path, "r") as f:
70+
stream = InputStream(f)
71+
while 1:
72+
line, comment, lines = stream.next_fortran_line()
73+
if not lines:
74+
break
75+
line = line.rstrip()
76+
comment = join_comments(comment)
77+
78+
if comment.strip().lower().replace("-", "").replace(" ", "") in [
79+
"!return"
80+
]:
81+
continue
82+
elif "end subroutine" in line or "end function" in line:
83+
for i, fl in enumerate(reversed(flines)):
84+
l = fl.strip()
85+
if not any(l):
86+
continue
87+
elif l == "return":
88+
del flines[len(flines) - i - 1]
89+
break
90+
flines.extend(lines)
91+
92+
with open(path, "w") as f:
93+
for line in flines:
94+
f.write(line.rstrip() + "\n")
9395

9496
@staticmethod
95-
def no_empty_comments(path, overwrite=False):
96-
"""Remove comments on lines with only whitespace"""
97-
# todo
98-
pass
97+
def cleanup_comments(path):
98+
"""
99+
Remove comments on lines with only whitespace, remove '--' from the beginnings
100+
of comments, make sure comment spacing is consistent (one space after '!'),
101+
remove horizontal dividers consisting of '-' or '*', remove 'SPECIFICATION'
102+
"""
103+
104+
flines = []
105+
with open(path, "r") as f:
106+
stream = InputStream(f)
107+
while 1:
108+
line, comment, lines = stream.next_fortran_line()
109+
if not lines:
110+
break
111+
line = line.rstrip()
112+
comment = join_comments(comment)
113+
nspaces = len(lines[0]) - len(lines[0].lstrip())
114+
indent = "".join(repeat(" ", nspaces))
115+
116+
if comment.startswith("#"):
117+
# preprocessor directives
118+
flines.extend(lines)
119+
elif not any(line):
120+
if any(pattern in comment for pattern in ["!!", "!<", "!>"]):
121+
flines.extend(lines)
122+
elif "SPECIFICATIONS" in comment:
123+
continue
124+
elif any(set(comment) & ALPHANUMERICS):
125+
comment = comment.strip().replace("--", "")
126+
i = 0
127+
for c in comment:
128+
if c.isdigit() or c.isalnum():
129+
break
130+
i += 1
131+
comment = f"! {comment[i:]}"
132+
flines.append(indent + comment)
133+
elif "-" in comment or "*" in comment:
134+
continue
135+
else:
136+
flines.append("")
137+
else:
138+
flines.extend(lines)
139+
140+
with open(path, "w") as f:
141+
for line in flines:
142+
f.write(line.rstrip() + "\n")
99143

100144

101-
def reformat(path, overwrite, separate_lines, no_return_statements, no_empty_comments):
145+
def reformat(
146+
path,
147+
separate_lines,
148+
trailing_returns,
149+
cleanup_comments,
150+
):
102151
if separate_lines:
103-
Transforms.separate_lines(path, overwrite=overwrite)
104-
if no_return_statements:
105-
Transforms.no_return_statements(path, overwrite=overwrite)
106-
warn("--no-return not implemented yet")
107-
if no_empty_comments:
108-
Transforms.no_empty_comments(path, overwrite=overwrite)
109-
warn("--no-empty-comments not implemented yet")
152+
Rules.separate_lines(path)
153+
if trailing_returns:
154+
Rules.trailing_returns(path)
155+
if cleanup_comments:
156+
Rules.cleanup_comments(path)
110157

111158

112159
if __name__ == "__main__":
@@ -117,43 +164,32 @@ def reformat(path, overwrite, separate_lines, no_return_statements, no_empty_com
117164
styles.
118165
"""
119166
)
120-
parser.add_argument(
121-
"-i", "--input", help="path to input file" # todo: or directory
122-
)
123-
parser.add_argument(
124-
"-f",
125-
"--force",
126-
action="store_true",
127-
default=False,
128-
required=False,
129-
help="overwrite/reformat files",
130-
)
167+
parser.add_argument("path")
131168
parser.add_argument(
132169
"--separate-lines",
133170
action="store_true",
134171
default=True,
135172
required=False,
136-
help="define variables on separate lines",
173+
help="Define dummy arguments and local variables on separate lines.",
137174
)
138175
parser.add_argument(
139-
"--no-return_statements",
176+
"--trailing-returns",
140177
action="store_true",
141-
default=False,
178+
default=True,
142179
required=False,
143-
help="no return statements at the end of routines",
180+
help="Remove return statements at the end of routines.",
144181
)
145182
parser.add_argument(
146-
"--no-empty-comments",
183+
"--cleanup-comments",
147184
action="store_true",
148-
default=False,
185+
default=True,
149186
required=False,
150-
help="no empty comments",
187+
help="Remove empty comments (containing only '!', or '!' followed by some number of '-' or '='), remove double dashes from beginnings of comments (e.g., '! -- comment' becomes '! comment'), and make internal comment spacing consistent (one space after '!' before text begins).",
151188
)
152189
args = parser.parse_args()
153190
reformat(
154-
path=Path(args.input).expanduser().absolute(),
155-
overwrite=args.force,
191+
path=Path(args.path).expanduser().absolute(),
156192
separate_lines=args.separate_lines,
157-
no_return_statements=args.no_return_statements,
158-
no_empty_comments=args.no_empty_comments,
193+
trailing_returns=args.trailing_returns,
194+
cleanup_comments=args.cleanup_comments,
159195
)

.github/common/wide_compat_reports.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
and makes a markdown table from the wide format report.
44
"""
55

6+
import sys
67
from pathlib import Path
8+
79
import pandas as pd
8-
import sys
910

1011
ip = Path(sys.argv[1]) # input file path
1112
op = Path(sys.argv[2]) # output file path

0 commit comments

Comments
 (0)