Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

[![Build Status](https://travis-ci.com/pseewald/fprettify.svg?branch=master)](https://travis-ci.com/pseewald/fprettify) [![Coverage Status](https://coveralls.io/repos/github/pseewald/fprettify/badge.svg?branch=master)](https://coveralls.io/github/pseewald/fprettify?branch=master) [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) [![PyPI version](https://badge.fury.io/py/fprettify.svg)](https://badge.fury.io/py/fprettify)

fprettify is an auto-formatter for modern Fortran code that imposes strict whitespace formatting, written in Python.
This is a fork of [pseewald/fprettify](https://github.com/pseewald/fprettify),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zaikunzhang you probably didn't mean to include changes to README in this PR.

Copy link

@beddalumia beddalumia May 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'm one of those people that could really use the --keep-blank-lines option, for that I contribute to some projects where (many) consecutive blank lines between subroutines are pervasively used (and expected in contributions, at least preserved).

@zaikunzhang, thank you very much for the proposal, can you remove the committed edit to the README, so that the PR could be merged (and hopefully the PyPi version bumped)?

which is an auto-formatter for modern Fortran code that imposes strict whitespace formatting, written in Python.


## Differences from pseewald/fprettify

* A new option `--disable-indent-subroutine` to disable the indentation after subroutines and functions.
* A new option `--keep-blank-lines` to disable the removal of consecutive/trailing blank lines.
* Avoid greedy regex, which is very slow for long numbers. See [Fixes and new options #99](https://github.com/pseewald/fprettify/pull/99/commits/2f766f33ed795ee69d0ac404e699e5475ff52f3f) of pseewald/fprettify.

## Features

* Auto-indentation.
Expand Down Expand Up @@ -68,11 +75,6 @@ end program

## Installation

The latest release can be installed using pip:
```
pip install --upgrade fprettify
```

Installation from source requires Python Setuptools:
```
./setup.py install
Expand Down
99 changes: 76 additions & 23 deletions fprettify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,31 +301,59 @@ def search(self, line):

forall_parser = where_parser

def build_scope_parser(fypp=True, mod=True):
def build_scope_parser(fypp=True, mod=True, subr=True):
# Zaikun's comment (2021-05-21)
# build_scope_parser builds a "parser" for the environments that will be indented. There are 3 special cases:
# 1. If and only if mod=True, the blocks of [module, program] will be indented.
# 2. If and only if subr=True, the blocks of [subroutine, function] will be indented.
# 3. If and only if fypp=True, the fypp preprocessor blocks will be indented.
parser = {}
# Zaikun's modification 1 >>
#parser['new'] = \
# [parser_re(IF_RE), parser_re(DO_RE), parser_re(SELCASE_RE), parser_re(SUBR_RE),
# parser_re(FCT_RE),
# parser_re(INTERFACE_RE), parser_re(TYPE_RE), parser_re(ENUM_RE), parser_re(ASSOCIATE_RE),
# None, parser_re(BLK_RE), where_parser(WHERE_RE), forall_parser(FORALL_RE)]
#
#parser['continue'] = \
# [parser_re(ELSE_RE), None, parser_re(CASE_RE), parser_re(CONTAINS_RE),
# parser_re(CONTAINS_RE),
# None, parser_re(CONTAINS_RE), None, None,
# None, None, parser_re(ELSEWHERE_RE), None]
#
#parser['end'] = \
# [parser_re(ENDIF_RE), parser_re(ENDDO_RE), parser_re(ENDSEL_RE), parser_re(ENDSUBR_RE),
# parser_re(ENDFCT_RE),
# parser_re(ENDINTERFACE_RE), parser_re(ENDTYPE_RE), parser_re(ENDENUM_RE), parser_re(ENDASSOCIATE_RE),
# parser_re(ENDANY_RE,spec=False), parser_re(ENDBLK_RE), parser_re(ENDWHERE_RE), parser_re(ENDFORALL_RE)]
parser['new'] = \
[parser_re(IF_RE), parser_re(DO_RE), parser_re(SELCASE_RE), parser_re(SUBR_RE),
parser_re(FCT_RE),
[parser_re(IF_RE), parser_re(DO_RE), parser_re(SELCASE_RE),
parser_re(INTERFACE_RE), parser_re(TYPE_RE), parser_re(ENUM_RE), parser_re(ASSOCIATE_RE),
None, parser_re(BLK_RE), where_parser(WHERE_RE), forall_parser(FORALL_RE)]

parser['continue'] = \
[parser_re(ELSE_RE), None, parser_re(CASE_RE), parser_re(CONTAINS_RE),
parser_re(CONTAINS_RE),
[parser_re(ELSE_RE), None, parser_re(CASE_RE),
None, parser_re(CONTAINS_RE), None, None,
None, None, parser_re(ELSEWHERE_RE), None]

parser['end'] = \
[parser_re(ENDIF_RE), parser_re(ENDDO_RE), parser_re(ENDSEL_RE), parser_re(ENDSUBR_RE),
parser_re(ENDFCT_RE),
[parser_re(ENDIF_RE), parser_re(ENDDO_RE), parser_re(ENDSEL_RE),
parser_re(ENDINTERFACE_RE), parser_re(ENDTYPE_RE), parser_re(ENDENUM_RE), parser_re(ENDASSOCIATE_RE),
parser_re(ENDANY_RE,spec=False), parser_re(ENDBLK_RE), parser_re(ENDWHERE_RE), parser_re(ENDFORALL_RE)]
# << Zaikun's modification 1

if mod:
parser['new'].extend([parser_re(MOD_RE), parser_re(SMOD_RE), parser_re(PROG_RE)])
parser['continue'].extend([parser_re(CONTAINS_RE), parser_re(CONTAINS_RE), parser_re(CONTAINS_RE)])
parser['end'].extend([parser_re(ENDMOD_RE), parser_re(ENDSMOD_RE), parser_re(ENDPROG_RE)])

# Zaikun's modification 1 >>
if subr:
parser['new'].extend([parser_re(SUBR_RE), parser_re(FCT_RE)])
parser['continue'].extend([parser_re(CONTAINS_RE), parser_re(CONTAINS_RE)])
parser['end'].extend([parser_re(ENDSUBR_RE), parser_re(ENDFCT_RE)])
# << Zaikun's modification 1

if fypp:
parser['new'].extend(PREPRO_NEW_SCOPE)
parser['continue'].extend(PREPRO_CONTINUE_SCOPE)
Expand Down Expand Up @@ -472,7 +500,11 @@ def build_scope_parser(fypp=True, mod=True):

## F90_CONSTANTS_TYPES_RE = re.compile(r"\b" + F90_NUMBER_ALL_RE + "_(" + "|".join([a + r"\b" for a in (
F90_CONSTANTS_TYPES_RE = re.compile(
r"(" + F90_NUMBER_ALL_RE + ")*_(" + "|".join((
# Zaikun's modification 3 >>
# Avoid greedy regex, which is very slow for long numbers.
#r"(" + F90_NUMBER_ALL_RE + ")*_(" + "|".join((
r"(" + F90_NUMBER_ALL_RE + ")_(" + "|".join((
# << Zaikun's modification
## F2003 iso_fortran_env constants.
## F2003 iso_c_binding constants.
"c_int", "c_short", "c_long", "c_long_long", "c_signed_char",
Expand Down Expand Up @@ -1043,19 +1075,20 @@ def format_single_fline(f_line, whitespace, whitespace_dict, linebreak_pos,
'print': 6, # 6: print / read statements
'type': 7, # 7: select type components
'intrinsics': 8, # 8: intrinsics
'decl': 9 # 9: declarations
'decl': 9, # 9: declarations
'use_only': 10
}

if whitespace == 0:
spacey = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
spacey = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
elif whitespace == 1:
spacey = [1, 1, 1, 1, 0, 0, 1, 0, 1, 1]
spacey = [1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1]
elif whitespace == 2:
spacey = [1, 1, 1, 1, 1, 0, 1, 0, 1, 1]
spacey = [1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1]
elif whitespace == 3:
spacey = [1, 1, 1, 1, 1, 1, 1, 0, 1, 1]
spacey = [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
elif whitespace == 4:
spacey = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
spacey = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
else:
raise NotImplementedError("unknown value for whitespace")

Expand Down Expand Up @@ -1314,8 +1347,8 @@ def add_whitespace_context(line, spacey):

# format ':' for labels and use only statements
if USE_RE.search(line):
line = re.sub(r'(only)\s*:\s*', r'\g<1>:' + ' ' *
spacey[0], line, flags=RE_FLAGS)
line = re.sub(r'(only)\s*:\s*', r'\g<1>' + ' ' * spacey[10] + ':' + ' ' * spacey[0], line, flags=RE_FLAGS)


return line

Expand Down Expand Up @@ -1419,7 +1452,8 @@ def reformat_inplace(filename, stdout=False, diffonly=False, **kwargs): # pragm
def reformat_ffile(infile, outfile, impose_indent=True, indent_size=3, strict_indent=False, impose_whitespace=True,
case_dict={},
impose_replacements=False, cstyle=False, whitespace=2, whitespace_dict={}, llength=132,
strip_comments=False, format_decl=False, orig_filename=None, indent_fypp=True, indent_mod=True):
strip_comments=False, format_decl=False, orig_filename=None, indent_fypp=True, indent_mod=True,
indent_subroutine=True, keep_blank_lines=False):
"""main method to be invoked for formatting a Fortran file."""

# note: whitespace formatting and indentation may require different parsing rules
Expand All @@ -1442,7 +1476,8 @@ def reformat_ffile(infile, outfile, impose_indent=True, indent_size=3, strict_in
reformat_ffile_combined(oldfile, newfile, _impose_indent, indent_size, strict_indent, impose_whitespace,
case_dict,
impose_replacements, cstyle, whitespace, whitespace_dict, llength,
strip_comments, format_decl, orig_filename, indent_fypp, indent_mod)
strip_comments, format_decl, orig_filename, indent_fypp, indent_mod,
indent_subroutine, keep_blank_lines)
oldfile = newfile

# 2) indentation
Expand All @@ -1455,7 +1490,8 @@ def reformat_ffile(infile, outfile, impose_indent=True, indent_size=3, strict_in
reformat_ffile_combined(oldfile, newfile, impose_indent, indent_size, strict_indent, _impose_whitespace,
case_dict,
_impose_replacements, cstyle, whitespace, whitespace_dict, llength,
strip_comments, format_decl, orig_filename, indent_fypp, indent_mod)
strip_comments, format_decl, orig_filename, indent_fypp, indent_mod,
indent_subroutine, keep_blank_lines)


outfile.write(newfile.getvalue())
Expand All @@ -1464,7 +1500,8 @@ def reformat_ffile(infile, outfile, impose_indent=True, indent_size=3, strict_in
def reformat_ffile_combined(infile, outfile, impose_indent=True, indent_size=3, strict_indent=False, impose_whitespace=True,
case_dict={},
impose_replacements=False, cstyle=False, whitespace=2, whitespace_dict={}, llength=132,
strip_comments=False, format_decl=False, orig_filename=None, indent_fypp=True, indent_mod=True):
strip_comments=False, format_decl=False, orig_filename=None, indent_fypp=True, indent_mod=True,
indent_subroutine=True, keep_blank_lines=False):

if not orig_filename:
orig_filename = infile.name
Expand All @@ -1480,7 +1517,7 @@ def reformat_ffile_combined(infile, outfile, impose_indent=True, indent_size=3,

if not has_fypp: indent_fypp = False

scope_parser = build_scope_parser(fypp=indent_fypp, mod=indent_mod)
scope_parser = build_scope_parser(fypp=indent_fypp, mod=indent_mod, subr=indent_subroutine)

# initialization

Expand Down Expand Up @@ -1608,7 +1645,7 @@ def reformat_ffile_combined(infile, outfile, impose_indent=True, indent_size=3,

# rm subsequent blank lines
skip_blank = EMPTY_RE.search(
f_line) and not any(comments) and not is_omp_conditional and not label
f_line) and not any(comments) and not is_omp_conditional and not label and not keep_blank_lines


def format_comments(lines, comments, strip_comments):
Expand Down Expand Up @@ -1986,6 +2023,8 @@ def get_arg_parser(args):
help="boolean, en-/disable whitespace for select type components")
parser.add_argument("--whitespace-intrinsics", type=str2bool, nargs="?", default="None", const=True,
help="boolean, en-/disable whitespace for intrinsics like if/write/close")
parser.add_argument("--whitespace-use-only", type=str2bool, nargs="?", default="None", const=True,
help="boolean, en-/disable whitespace for the colon after `use MODULE, only`")
parser.add_argument("--strict-indent", action='store_true', default=False, help="strictly impose indentation even for nested loops")
parser.add_argument("--enable-decl", action="store_true", default=False, help="enable whitespace formatting of declarations ('::' operator).")
parser.add_argument("--disable-indent", action='store_true', default=False, help="don't impose indentation")
Expand All @@ -2004,6 +2043,17 @@ def get_arg_parser(args):
parser.add_argument('--disable-indent-mod', action='store_true', default=False,
help="Disables the indentation after module / program.")

# Zaikun's modification 1 >>
# A new option "--disable-indent-subroutine" to disable the indentation after subroutines/functions.
parser.add_argument('--disable-indent-subroutine', action='store_true', default=False,
help="Disables the indentation after subroutine / function.")
# << Zaikun's modification 1

# Zaikun's modification 2 >>
# A new option "--keep-blank-lines" to disable the removal of consecutive/trailing blank lines.
parser.add_argument("--keep-blank-lines", action='store_true', default=False, help="Disables the removal of consecutive/trailing blank lines.")
# << Zaikun's modification 2

parser.add_argument("-d","--diff", action='store_true', default=False,
help="Write file differences to stdout instead of formatting inplace")
parser.add_argument("-s", "--stdout", action='store_true', default=False,
Expand Down Expand Up @@ -2044,6 +2094,7 @@ def build_ws_dict(args):
ws_dict['print'] = args.whitespace_print
ws_dict['type'] = args.whitespace_type
ws_dict['intrinsics'] = args.whitespace_intrinsics
ws_dict['use_only'] = args.whitespace_use_only
return ws_dict

# support legacy input:
Expand Down Expand Up @@ -2136,7 +2187,9 @@ def build_ws_dict(args):
strip_comments=file_args.strip_comments,
format_decl=file_args.enable_decl,
indent_fypp=not file_args.disable_fypp,
indent_mod=not file_args.disable_indent_mod)
indent_mod=not file_args.disable_indent_mod,
indent_subroutine=not file_args.disable_indent_subroutine,
keep_blank_lines=file_args.keep_blank_lines)
except FprettifyException as e:
log_exception(e, "Fatal error occured")
sys.exit(1)