1
1
import argparse
2
- import re
3
- from contextlib import nullcontext
2
+ import string
4
3
from itertools import repeat
5
4
from pathlib import Path
6
- from typing import Iterator , Optional
7
- from warnings import warn
8
5
9
6
from fprettify .fparse_utils import InputStream
10
7
11
- INTENT_PATTERN = re . compile ( r".*(intent\(.+\)).*" )
8
+ ALPHANUMERICS = set ( string . ascii_letters + string . digits )
12
9
13
10
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 )])
17
13
18
14
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 :
31
16
@staticmethod
32
- def separate_lines (path , overwrite = False ):
17
+ def separate_lines (path ):
33
18
"""Variables defined on separate lines"""
34
19
35
20
flines = []
36
21
with open (path , "r" ) as f :
37
22
stream = InputStream (f )
38
23
while 1 :
39
- line , comments , lines = stream .next_fortran_line ()
24
+ line , comment , lines = stream .next_fortran_line ()
40
25
if not lines :
41
26
break
42
27
line = line .rstrip ()
43
28
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 )
48
37
continue
49
38
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
56
42
57
43
if not line :
58
44
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 ]
61
50
):
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 )
67
52
else :
68
- for s in split :
69
- if s . strip () == "&" :
53
+ for s in vars :
54
+ if s == "&" :
70
55
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 )
84
59
60
+ with open (path , "w" ) as f :
85
61
for line in flines :
86
- write (line )
62
+ f . write (line . rstrip () + " \n " )
87
63
88
64
@staticmethod
89
- def no_return_statements (path , overwrite = False ):
65
+ def trailing_returns (path ):
90
66
"""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 " )
93
95
94
96
@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 " )
99
143
100
144
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
+ ):
102
151
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 )
110
157
111
158
112
159
if __name__ == "__main__" :
@@ -117,43 +164,32 @@ def reformat(path, overwrite, separate_lines, no_return_statements, no_empty_com
117
164
styles.
118
165
"""
119
166
)
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" )
131
168
parser .add_argument (
132
169
"--separate-lines" ,
133
170
action = "store_true" ,
134
171
default = True ,
135
172
required = False ,
136
- help = "define variables on separate lines" ,
173
+ help = "Define dummy arguments and local variables on separate lines. " ,
137
174
)
138
175
parser .add_argument (
139
- "--no-return_statements " ,
176
+ "--trailing-returns " ,
140
177
action = "store_true" ,
141
- default = False ,
178
+ default = True ,
142
179
required = False ,
143
- help = "no return statements at the end of routines" ,
180
+ help = "Remove return statements at the end of routines. " ,
144
181
)
145
182
parser .add_argument (
146
- "--no-empty -comments" ,
183
+ "--cleanup -comments" ,
147
184
action = "store_true" ,
148
- default = False ,
185
+ default = True ,
149
186
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). " ,
151
188
)
152
189
args = parser .parse_args ()
153
190
reformat (
154
- path = Path (args .input ).expanduser ().absolute (),
155
- overwrite = args .force ,
191
+ path = Path (args .path ).expanduser ().absolute (),
156
192
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 ,
159
195
)
0 commit comments