#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################### # This file is part of fprettify. # Copyright (C) 2016-2019 Patrick Seewald, CP2K developers group # # fprettify is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # fprettify is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with fprettify. If not, see . ############################################################################### """ Impose white space conventions and indentation based on scopes / subunits normalization of white spaces supported for following operators: - relational operators: .EQ. .NE. .LT. .LE. .GT. .GE. == /= < <= > >= - logical operators: .AND. .OR. .EQV. .NEQV. .NOT. - bracket delimiters - commas and semicolons: - arithmetic operators: * / ** + - - other operators: % - (sign) = (function argument) = (assignment) => (pointer assignment) supported criteria for alignment / indentation: Fortran lines: - if, else, endif - do, enddo - select case, case, end select - select rank, rank, end select - subroutine, end subroutine - function, end function - module, end module - program, end program - interface, end interface - type, end type Actual lines (parts of Fortran lines separated by linebreaks): - bracket delimiters (.), (/./), and [.] - assignments by value = and pointer =>. LIMITATIONS - assumes that all subunits are explicitly ended within same file, no treatment of #include statements - can not deal with f77 constructs (files are ignored) FIXME's - internal errors should not happen - wrap regular expression parser. This allows to extend parser by constructs that are not regular expressions (and support e.g. forall construct). - strip whitespaces once and for all and then assume no trailing / leading whitespaces - open files only when needed """ import re import sys import logging import os import io sys.stdin = io.TextIOWrapper( sys.stdin.detach(), encoding='UTF-8', line_buffering=True) sys.stdout = io.TextIOWrapper( sys.stdout.detach(), encoding='UTF-8', line_buffering=True) from .fparse_utils import (VAR_DECL_RE, OMP_COND_RE, InputStream, CharFilter, FprettifyException, FprettifyParseException, FprettifyInternalException, CPP_RE, NOTFORTRAN_LINE_RE, NOTFORTRAN_FYPP_LINE_RE, FYPP_LINE_RE, RE_FLAGS, STR_OPEN_RE, parser_re, FYPP_WITHOUT_PREPRO_RE) # recognize fortran files by extension FORTRAN_EXTENSIONS = [".f", ".for", ".ftn", ".f90", ".f95", ".f03", ".fpp"] FORTRAN_EXTENSIONS += [_.upper() for _ in FORTRAN_EXTENSIONS] # constants, mostly regular expressions: FORMATTER_ERROR_MESSAGE = (" Wrong usage of formatting-specific directives" " '&', '!&', '!&<' or '!&>'.") LINESPLIT_MESSAGE = ("auto indentation failed due to chars limit, " "line should be split") EOL_STR = r"\s*;?\s*$" # end of fortran line EOL_SC = r"\s*;\s*$" # whether line is ended with semicolon SOL_STR = r"^\s*" # start of fortran line STATEMENT_LABEL_RE = re.compile(r"^\s*(\d+)", RE_FLAGS) # regular expressions for parsing statements that start, continue or end a # subunit: IF_RE = re.compile( SOL_STR + r"(\w+\s*:)?\s*IF\s*\(.*\)\s*THEN" + EOL_STR, RE_FLAGS) ELSE_RE = re.compile( SOL_STR + r"ELSE(\s*IF\s*\(.*\)\s*THEN)?" + EOL_STR, RE_FLAGS) ENDIF_RE = re.compile(SOL_STR + r"END\s*IF(\s+\w+)?" + EOL_STR, RE_FLAGS) DO_RE = re.compile(SOL_STR + r"(\w+\s*:)?\s*DO(" + EOL_STR + r"|\s+\w)", RE_FLAGS) ENDDO_RE = re.compile(SOL_STR + r"END\s*DO(\s+\w+)?" + EOL_STR, RE_FLAGS) SELCASE_RE = re.compile( SOL_STR + r"SELECT\s*(CASE|RANK|TYPE)\s*\(.*\)" + EOL_STR, RE_FLAGS) CASE_RE = re.compile( SOL_STR + r"((CASE|RANK|TYPE\s+IS|CLASS\s+IS)\s*(\(.*\)|DEFAULT)|CLASS\s+DEFAULT)" + EOL_STR, RE_FLAGS) ENDSEL_RE = re.compile(SOL_STR + r"END\s*SELECT" + EOL_STR, RE_FLAGS) ASSOCIATE_RE = re.compile(SOL_STR + r"ASSOCIATE\s*\(.*\)" + EOL_STR, RE_FLAGS) ENDASSOCIATE_RE = re.compile(SOL_STR + r"END\s*ASSOCIATE" + EOL_STR, RE_FLAGS) BLK_RE = re.compile(SOL_STR + r"(\w+\s*:)?\s*BLOCK" + EOL_STR, RE_FLAGS) ENDBLK_RE = re.compile(SOL_STR + r"END\s*BLOCK(\s+\w+)?" + EOL_STR, RE_FLAGS) SUBR_RE = re.compile( r"^([^\"']* )?SUBROUTINE\s+\w+\s*(\(.*\))?" + EOL_STR, RE_FLAGS) ENDSUBR_RE = re.compile( SOL_STR + r"END\s*SUBROUTINE(\s+\w+)?" + EOL_STR, RE_FLAGS) FCT_RE = re.compile( r"^([^\"']* )?FUNCTION\s+\w+\s*(\(.*\))?(\s*RESULT\s*\(\w+\))?" + EOL_STR, RE_FLAGS) ENDFCT_RE = re.compile( SOL_STR + r"END\s*FUNCTION(\s+\w+)?" + EOL_STR, RE_FLAGS) MOD_RE = re.compile(SOL_STR + r"MODULE\s+\w+" + EOL_STR, RE_FLAGS) ENDMOD_RE = re.compile(SOL_STR + r"END\s*MODULE(\s+\w+)?" + EOL_STR, RE_FLAGS) SMOD_RE = re.compile(SOL_STR + r"SUBMODULE\s*\(\w+\)\s+\w+" + EOL_STR, RE_FLAGS) ENDSMOD_RE = re.compile(SOL_STR + r"END\s*SUBMODULE(\s+\w+)?" + EOL_STR, RE_FLAGS) TYPE_RE = re.compile( SOL_STR + r"TYPE(\s*,\s*(BIND\s*\(\s*C\s*\)|EXTENDS\s*\(.*\)|ABSTRACT|PUBLIC|PRIVATE))*(\s*,\s*)?(\s*::\s*|\s+)\w+" + EOL_STR, RE_FLAGS) ENDTYPE_RE = re.compile(SOL_STR + r"END\s*TYPE(\s+\w+)?" + EOL_STR, RE_FLAGS) PROG_RE = re.compile(SOL_STR + r"PROGRAM\s+\w+" + EOL_STR, RE_FLAGS) ENDPROG_RE = re.compile( SOL_STR + r"END\s*PROGRAM(\s+\w+)?" + EOL_STR, RE_FLAGS) INTERFACE_RE = re.compile( r"^([^\"']* )?INTERFACE(\s+\w+|\s+(OPERATOR|ASSIGNMENT)\s*\(.*\))?" + EOL_STR, RE_FLAGS) ENDINTERFACE_RE = re.compile( SOL_STR + r"END\s*INTERFACE(\s+\w+|\s+(OPERATOR|ASSIGNMENT)\s*\(.*\))?" + EOL_STR, RE_FLAGS) CONTAINS_RE = re.compile(SOL_STR + r"CONTAINS" + EOL_STR, RE_FLAGS) ENUM_RE = re.compile( SOL_STR + r"ENUM(\s*,\s*(BIND\s*\(\s*C\s*\)))?((\s*::\s*|\s+)\w+)?" + EOL_STR, RE_FLAGS) ENDENUM_RE = re.compile(SOL_STR + r"END\s*ENUM(\s+\w+)?" + EOL_STR, RE_FLAGS) ENDANY_RE = re.compile(SOL_STR + r"END" + EOL_STR, RE_FLAGS) # Regular expressions for where and forall block constructs FORALL_RE = re.compile(SOL_STR + r"(\w+\s*:)?\s*FORALL\s*\(.*\)" + EOL_STR, RE_FLAGS) ENDFORALL_RE = re.compile(SOL_STR + r"END\s*FORALL(\s+\w+)?" + EOL_STR, RE_FLAGS) WHERE_RE = re.compile(SOL_STR + r"(\w+\s*:)?\s*WHERE\s*\(.*\)" + EOL_STR, RE_FLAGS) ELSEWHERE_RE = re.compile(SOL_STR + r"ELSE\s*WHERE(\(.*\))?(\s*\w+)?" + EOL_STR, RE_FLAGS) ENDWHERE_RE = re.compile(SOL_STR + r"END\s*WHERE(\s+\w+)?" + EOL_STR, RE_FLAGS) # Regular expressions for preprocessor directives FYPP_DEF_RE = re.compile(SOL_STR + r"#:DEF\s+", RE_FLAGS) FYPP_ENDDEF_RE = re.compile(SOL_STR + r"#:ENDDEF", RE_FLAGS) FYPP_IF_RE = re.compile(SOL_STR + r"#:IF\s+", RE_FLAGS) FYPP_ELIF_ELSE_RE = re.compile(SOL_STR + r"#:(ELIF\s+|ELSE)", RE_FLAGS) FYPP_ENDIF_RE = re.compile(SOL_STR + r"#:ENDIF", RE_FLAGS) FYPP_FOR_RE = re.compile(SOL_STR + r"#:FOR\s+", RE_FLAGS) FYPP_ENDFOR_RE = re.compile(SOL_STR + r"#:ENDFOR", RE_FLAGS) FYPP_BLOCK_RE = re.compile(SOL_STR + r"#:BLOCK\s+", RE_FLAGS) FYPP_ENDBLOCK_RE = re.compile(SOL_STR + r"#:ENDBLOCK", RE_FLAGS) FYPP_CALL_RE = re.compile(SOL_STR + r"#:CALL\s+", RE_FLAGS) FYPP_ENDCALL_RE = re.compile(SOL_STR + r"#:ENDCALL", RE_FLAGS) FYPP_MUTE_RE = re.compile(SOL_STR + r"#:MUTE", RE_FLAGS) FYPP_ENDMUTE_RE = re.compile(SOL_STR + r"#:ENDMUTE", RE_FLAGS) PRIVATE_RE = re.compile(SOL_STR + r"PRIVATE\s*::", RE_FLAGS) PUBLIC_RE = re.compile(SOL_STR + r"PUBLIC\s*::", RE_FLAGS) END_RE = re.compile(SOL_STR + r"(END)\s*(IF|DO|SELECT|ASSOCIATE|BLOCK|SUBROUTINE|FUNCTION|MODULE|SUBMODULE|TYPE|PROGRAM|INTERFACE|ENUM|WHERE|FORALL)", RE_FLAGS) # intrinsic statements with parenthesis notation that are not functions INTR_STMTS_PAR = (r"(ALLOCATE|DEALLOCATE|" r"OPEN|CLOSE|READ|WRITE|" r"FLUSH|ENDFILE|REWIND|BACKSPACE|INQUIRE|" r"FORALL|WHERE|ASSOCIATE|NULLIFY)") # regular expressions for parsing linebreaks LINEBREAK_STR = r"(&)[\s]*(?:!.*)?$" # regular expressions for parsing operators # Note: +/- in real literals and sign operator is ignored PLUSMINUS_RE = re.compile(r"(?<=[\w\)\]])\s*(\+|-)\s*", RE_FLAGS) # Note: ** or // (or any multiples of * or /) are ignored # we also ignore any * or / before a :: because we may be seeing 'real*8' MULTDIV_RE = re.compile( r"(?<=[\w\)\]])\s*((?(?!=)|>=))\s*(?!\))", RE_FLAGS) LOG_OP_RE = re.compile(r"\s*(\.(?:AND|OR|EQV|NEQV)\.)\s*", RE_FLAGS) PRINT_RE = re.compile(r"(?:(?<=\bPRINT)|(?<=\bREAD))\s*(\*,?)\s*", RE_FLAGS) # regular expressions for parsing delimiters DEL_OPEN_STR = r"(\(\/?|\[)" DEL_OPEN_RE = re.compile(r"^" + DEL_OPEN_STR, RE_FLAGS) DEL_CLOSE_STR = r"(\/?\)|\])" DEL_CLOSE_RE = re.compile(r"^" + DEL_CLOSE_STR, RE_FLAGS) # empty line regex EMPTY_RE = re.compile(SOL_STR + r"$", RE_FLAGS) PREPRO_NEW_SCOPE = [parser_re(FYPP_DEF_RE), parser_re(FYPP_IF_RE), parser_re(FYPP_FOR_RE), parser_re(FYPP_BLOCK_RE), parser_re(FYPP_CALL_RE), parser_re(FYPP_MUTE_RE)] PREPRO_CONTINUE_SCOPE = [None, parser_re(FYPP_ELIF_ELSE_RE), None, None, None, None] PREPRO_END_SCOPE = [parser_re(FYPP_ENDDEF_RE), parser_re(FYPP_ENDIF_RE), parser_re(FYPP_ENDFOR_RE), parser_re(FYPP_ENDBLOCK_RE), parser_re(FYPP_ENDCALL_RE), parser_re(FYPP_ENDMUTE_RE)] class plusminus_parser(parser_re): """parser for +/- in addition """ def __init__(self, regex): self._re = regex self._re_excl = re.compile(r"\b(\d+\.?\d*|\d*\.?\d+)[de]" + EOL_STR, RE_FLAGS) def split(self, line): partsplit = self._re.split(line) partsplit_out = [] # exclude splits due to '+/-' in real literals for n, part in enumerate(partsplit): if re.search(r"^(\+|-)$", part): if self._re_excl.search(partsplit[n-1]): if n==1: partsplit_out = [partsplit[n-1]] if n + 1 >= len(partsplit) or not partsplit_out: raise FprettifyParseException("non-standard expression involving + or -",'',0) partsplit_out[-1] += part + partsplit[n+1] else: if n==1: partsplit_out = [partsplit[n-1]] if n + 1 >= len(partsplit): raise FprettifyParseException("non-standard expression involving + or -",'',0) partsplit_out += [part, partsplit[n+1]] if not partsplit_out: partsplit_out = partsplit return partsplit_out # two-sided operators LR_OPS_RE = [REL_OP_RE, LOG_OP_RE, plusminus_parser(PLUSMINUS_RE), MULTDIV_RE, PRINT_RE] USE_RE = re.compile( SOL_STR + "USE(\s+|(,.+?)?::\s*)\w+?((,.+?=>.+?)+|,\s*only\s*:.+?)?$" + EOL_STR, RE_FLAGS) # markups to deactivate formatter NO_ALIGN_RE = re.compile(SOL_STR + r"&\s*[^\s*]+") class where_parser(parser_re): """parser for where / forall construct """ def search(self, line): match = self._re.search(line) if match: level = 0 for pos, char in CharFilter(line): [what_del_open, what_del_close] = get_curr_delim(line, pos) if what_del_open: if what_del_open.group() == r'(': level += 1 if what_del_close and what_del_close.group() == r')': if level == 1: if EMPTY_RE.search(line[pos+1:]): return True else: return False else: level += -1 return False forall_parser = where_parser def build_scope_parser(fypp=True, mod=True): parser = {} 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)] 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)]) if fypp: parser['new'].extend(PREPRO_NEW_SCOPE) parser['continue'].extend(PREPRO_CONTINUE_SCOPE) parser['end'].extend(PREPRO_END_SCOPE) return parser # match namelist names NML_RE = re.compile(r"(/\w+/)", RE_FLAGS) # find namelists and data statements NML_STMT_RE = re.compile(SOL_STR + r"NAMELIST.*/.*/", RE_FLAGS) DATA_STMT_RE = re.compile(SOL_STR + r"DATA\s+\w", RE_FLAGS) ## Regexp for f90 keywords' F90_KEYWORDS_RE = re.compile(r"\b(" + "|".join(( "allocatable", "allocate", "assign", "assignment", "backspace", "block", "call", "case", "character", "close", "common", "complex", "contains", "continue", "cycle", "data", "deallocate", "dimension", "do", "double", "else", "elseif", "elsewhere", "end", "enddo", "endfile", "endif", "entry", "equivalence", "exit", "external", "forall", "format", "function", "goto", "if", "implicit", "include", "inquire", "integer", "intent", "interface", "intrinsic", "logical", "module", "namelist", "none", "nullify", "only", "open", "operator", "optional", "parameter", "pause", "pointer", "precision", "print", "private", "procedure", "program", "public", "read", "real", "recursive", "result", "return", "rewind", "save", "select", "sequence", "stop", "subroutine", "target", "then", "type", "use", "where", "while", "write", ## F95 keywords. "elemental", "pure", ## F2003 "abstract", "associate", "asynchronous", "bind", "class", "deferred", "enum", "enumerator", "extends", "extends_type_of", "final", "generic", "import", "non_intrinsic", "non_overridable", "nopass", "pass", "protected", "same_type_as", "value", "volatile", ## F2008. "contiguous", "submodule", "concurrent", "codimension", "sync all", "sync memory", "critical", "image_index", )) + r")\b", RE_FLAGS) ## Regexp whose first part matches F90 intrinsic procedures. ## Add a parenthesis to avoid catching non-procedures. F90_PROCEDURES_RE = re.compile(r"\b(" + "|".join(( "abs", "achar", "acos", "adjustl", "adjustr", "aimag", "aint", "all", "allocated", "anint", "any", "asin", "associated", "atan", "atan2", "bit_size", "btest", "ceiling", "char", "cmplx", "conjg", "cos", "cosh", "count", "cshift", "date_and_time", "dble", "digits", "dim", "dot_product", "dprod", "eoshift", "epsilon", "exp", "exponent", "floor", "fraction", "huge", "iachar", "iand", "ibclr", "ibits", "ibset", "ichar", "ieor", "index", "int", "ior", "ishft", "ishftc", "kind", "lbound", "len", "len_trim", "lge", "lgt", "lle", "llt", "log", "log10", "logical", "matmul", "max", "maxexponent", "maxloc", "maxval", "merge", "min", "minexponent", "minloc", "minval", "mod", "modulo", "mvbits", "nearest", "nint", "not", "pack", "precision", "present", "product", "radix", ## Real is taken out here to avoid highlighting declarations. "random_number", "random_seed", "range", ## "real" "repeat", "reshape", "rrspacing", "scale", "scan", "selected_int_kind", "selected_real_kind", "set_exponent", "shape", "sign", "sin", "sinh", "size", "spacing", "spread", "sqrt", "sum", "system_clock", "tan", "tanh", "tiny", "transfer", "transpose", "trim", "ubound", "unpack", "verify", ## F95 intrinsic functions. "null", "cpu_time", ## F2003. "move_alloc", "command_argument_count", "get_command", "get_command_argument", "get_environment_variable", "selected_char_kind", "wait", "flush", "new_line", "extends", "extends_type_of", "same_type_as", "bind", ## F2003 ieee_arithmetic intrinsic module. "ieee_support_underflow_control", "ieee_get_underflow_mode", "ieee_set_underflow_mode", ## F2003 iso_c_binding intrinsic module. "c_loc", "c_funloc", "c_associated", "c_f_pointer", "c_f_procpointer", ## F2008. "bge", "bgt", "ble", "blt", "dshiftl", "dshiftr", "leadz", "popcnt", "poppar", "trailz", "maskl", "maskr", "shifta", "shiftl", "shiftr", "merge_bits", "iall", "iany", "iparity", "storage_size", "bessel_j0", "bessel_j1", "bessel_jn", "bessel_y0", "bessel_y1", "bessel_yn", "erf", "erfc", "erfc_scaled", "gamma", "hypot", "log_gamma", "norm2", "parity", "findloc", "is_contiguous", "sync images", "lock", "unlock", "image_index", "lcobound", "ucobound", "num_images", "this_image", ## F2008 iso_fortran_env module. "compiler_options", "compiler_version", ## F2008 iso_c_binding module. "c_sizeof" )) + r")\b", RE_FLAGS) F90_MODULES_RE = re.compile(r"\b(" + "|".join(( ## F2003/F2008 module names "iso_fortran_env", "iso_c_binding", "ieee_exceptions", "ieee_arithmetic", "ieee_features" )) + r")\b", RE_FLAGS) ## Regexp matching intrinsic operators F90_OPERATORS_RE = re.compile(r"(" + "|".join([r"\." + a + r"\." for a in ( "and", "eq", "eqv", "false", "ge", "gt", "le", "lt", "ne", "neqv", "not", "or", "true" )]) + r")", RE_FLAGS) ## Regexp for Fortran intrinsic constants F90_CONSTANTS_RE = re.compile(r"\b(" + "|".join(( ## F2003 iso_fortran_env constants. "input_unit", "output_unit", "error_unit", "iostat_end", "iostat_eor", "numeric_storage_size", "character_storage_size", "file_storage_size", ## F2003 iso_c_binding constants. "c_int", "c_short", "c_long", "c_long_long", "c_signed_char", "c_size_t", "c_int8_t", "c_int16_t", "c_int32_t", "c_int64_t", "c_int_least8_t", "c_int_least16_t", "c_int_least32_t", "c_int_least64_t", "c_int_fast8_t", "c_int_fast16_t", "c_int_fast32_t", "c_int_fast64_t", "c_intmax_t", "c_intptr_t", "c_float", "c_double", "c_long_double", "c_float_complex", "c_double_complex", "c_long_double_complex", "c_bool", "c_char", "c_null_char", "c_alert", "c_backspace", "c_form_feed", "c_new_line", "c_carriage_return", "c_horizontal_tab", "c_vertical_tab", "c_ptr", "c_funptr", "c_null_ptr", "c_null_funptr", ## F2008 iso_fortran_env constants. "character_kinds", "int8", "int16", "int32", "int64", "integer_kinds", "iostat_inquire_internal_unit", "logical_kinds", "real_kinds", "real32", "real64", "real128", "lock_type", "atomic_int_kind", "atomic_logical_kind", )) + r")\b", RE_FLAGS) F90_INT_RE = r"[-+]?[0-9]+" F90_FLOAT_RE = r"[-+]?([0-9]+\.[0-9]*|\.[0-9]+)" F90_NUMBER_RE = "(" + F90_INT_RE + "|" + F90_FLOAT_RE + ")" F90_FLOAT_EXP_RE = F90_NUMBER_RE + r"[eEdD]" + F90_NUMBER_RE F90_NUMBER_ALL_RE = "(" + F90_NUMBER_RE + "|" + F90_FLOAT_EXP_RE + ")" F90_NUMBER_ALL_REC = re.compile(F90_NUMBER_ALL_RE, RE_FLAGS) ## 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(( ## F2003 iso_fortran_env constants. ## F2003 iso_c_binding constants. "c_int", "c_short", "c_long", "c_long_long", "c_signed_char", "c_size_t", "c_int8_t", "c_int16_t", "c_int32_t", "c_int64_t", "c_int_least8_t", "c_int_least16_t", "c_int_least32_t", "c_int_least64_t", "c_int_fast8_t", "c_int_fast16_t", "c_int_fast32_t", "c_int_fast64_t", "c_intmax_t", "c_intptr_t", "c_float", "c_double", "c_long_double", "c_float_complex", "c_double_complex", "c_long_double_complex", "c_bool", "c_char", ## F2008 iso_fortran_env constants. "character_kinds", "int8", "int16", "int32", "int64", "integer_kinds", "logical_kinds", "real_kinds", "real32", "real64", "real128", "lock_type", "atomic_int_kind", "atomic_logical_kind", )) + r")\b", RE_FLAGS) class F90Indenter(object): """ Parses encapsulation of subunits / scopes line by line and updates the indentation. """ def __init__(self, scope_parser, first_indent, rel_indent, filename): # scopes / subunits: self._scope_storage = [] # indents for all fortran lines: self._indent_storage = [] # indents of actual lines of current fortran line self._line_indents = [] self._parser = scope_parser self._filename = filename self._aligner = F90Aligner(filename) # no lines have been processed yet: self._initial = True # implicit scopes: we define implicit scopes, as many as match # first_indent and rel_indent. This allows for, e.g., a properly # indented "END FUNCTION" without matching "FUNCTION" statement: if rel_indent > 0: for n_impl in range(first_indent % rel_indent, first_indent + 1, rel_indent): self._indent_storage += [n_impl] if not self._indent_storage: self._indent_storage = [0] def process_lines_of_fline(self, f_line, lines, rel_ind, rel_ind_con, line_nr, indent_fypp=True, manual_lines_indent=None): """ Process all lines that belong to a Fortran line `f_line`. Impose a relative indent of `rel_ind` for current Fortran line, and `rel_ind_con` for line continuation. By default line continuations are auto-aligned by F90Aligner :param f_line: fortran line :param lines: actual lines belonging to f_line :param rel_ind: relative scope indent size for this line :rel_ind_con: relative continuation indent size for this line :line_nr: line number :indent_fypp: whether or not to include fypp preprocessor lines :manual_lines_indent: don't use F90Aligner but manually impose indents for continuations """ if (self._initial and (PROG_RE.match(f_line) or MOD_RE.match(f_line))): self._indent_storage[-1] = 0 self._line_indents = [0] * len(lines) br_indent_list = [0] * len(lines) # local variables to avoid self hassle: line_indents = self._line_indents scopes = self._scope_storage indents = self._indent_storage filename = self._filename # check statements that start new scope is_new = False valid_new = False f_filter = CharFilter(f_line, filter_fypp=not indent_fypp) f_line_filtered = f_filter.filter_all() for new_n, newre in enumerate(self._parser['new']): if newre and newre.search(f_line_filtered) and \ not self._parser['end'][new_n].search(f_line_filtered): what_new = new_n is_new = True valid_new = True scopes.append(what_new) log_message("{}: {}".format(what_new, f_line), "debug", filename, line_nr) # check statements that continue scope is_con = False valid_con = False for con_n, conre in enumerate(self._parser['continue']): if conre and conre.search(f_line_filtered): what_con = con_n is_con = True log_message("{}: {}".format( what_con, f_line), "debug", filename, line_nr) if len(scopes) > 0: what = scopes[-1] if what == what_con: valid_con = True # check statements that end scope is_end = False valid_end = False for end_n, endre in enumerate(self._parser['end']): if endre and endre.search(f_line_filtered): what_end = end_n is_end = True log_message("{}: {}".format( what_end, f_line), "debug", filename, line_nr) if len(scopes) > 0: what = scopes.pop() if what == what_end or not self._parser['end'][what_end].spec: valid_end = True log_message("{}: {}".format( what_end, f_line), "debug", filename, line_nr) else: valid_end = True # fypp preprocessor scopes may be within continuation lines if indent_fypp and len(lines) > 1 and not FYPP_LINE_RE.search(f_line_filtered): for new_n, newre in enumerate(PREPRO_NEW_SCOPE): for l in lines: if(newre and newre.search(l)): is_new = True valid_new = True scopes.append(new_n) for end_n, endre in enumerate(PREPRO_END_SCOPE): for l in lines: if(endre and endre.search(l)): is_end = True valid_end = True if len(scopes) > 0: what = scopes.pop() # deal with line breaks if not manual_lines_indent: self._aligner.process_lines_of_fline( f_line, lines, rel_ind_con, line_nr) br_indent_list = self._aligner.get_lines_indent() else: br_indent_list = manual_lines_indent for pos in range(0, len(lines) - 1): line_indents[pos + 1] = br_indent_list[pos + 1] if is_new and not is_end: if not valid_new: log_message('invalid scope opening statement', "info", filename, line_nr) line_indents = [ind + indents[-1] for ind in line_indents] indents.append(rel_ind + indents[-1]) elif (not is_new) and (is_con or is_end): valid = valid_con if is_con else valid_end if not valid: line_indents = [ind + indents[-1] for ind in line_indents] log_message('invalid scope closing statement', "info", filename, line_nr) else: if len(indents) > 1 or self._initial: line_indents = [ind + indents[-2 + self._initial] for ind in line_indents] if is_end and valid: if len(indents) > 1: indents.pop() else: indents[-1] = 0 else: line_indents = [ind + indents[-1] for ind in line_indents] # we have processed first line: self._initial = False # reassigning self.* to the updated variables self._line_indents = line_indents self._scope_storage = scopes self._indent_storage = indents def get_fline_indent(self): """after processing, retrieve the indentation of the full Fortran line.""" return self._indent_storage[-1] def get_lines_indent(self): """after processing, retrieve the indents of all line parts.""" return self._line_indents class F90Aligner(object): """ Alignment of continuations of a broken line, based on the following heuristics: if line break in brackets We are parsing the level of nesting and align to most inner bracket delimiter. else if line is an assignment alignment to '=' or '=>'. note: assignment operator recognized as any '=' that is not part of another operator and that is not enclosed in bracket else if line is a declaration alignment to '::' else default indent """ def __init__(self, filename): self._filename = filename self.__init_line(0) def __init_line(self, line_nr): """initialization before processing new line""" self._line_nr = line_nr self._line_indents = [0] self._level = 0 self._br_indent_list = [0] def process_lines_of_fline(self, f_line, lines, rel_ind, line_nr): """ process all lines that belong to a Fortran line `f_line`, `rel_ind` is the relative indentation size. """ self.__init_line(line_nr) is_decl = VAR_DECL_RE.search(f_line) or PUBLIC_RE.search(f_line) or PRIVATE_RE.match(f_line) is_use = USE_RE.search(f_line) for pos, line in enumerate(lines): self.__align_line_continuations( line, is_decl, is_use, rel_ind, self._line_nr + pos) if pos + 1 < len(lines): self._line_indents.append(self._br_indent_list[-1]) if len(self._br_indent_list) > 2 or self._level: log_message('unpaired bracket delimiters', "info", self._filename, self._line_nr) def get_lines_indent(self): """after processing, retrieve the indents of all line parts.""" return self._line_indents def __align_line_continuations(self, line, is_decl, is_use, indent_size, line_nr): """align continuation lines.""" indent_list = self._br_indent_list level = self._level filename = self._filename pos_eq = 0 pos_ldelim = [] pos_rdelim = [] ldelim = [] rdelim = [] # find delimiters that are not ended on this line. # find proper alignment to most inner delimiter # or alignment to assignment operator rel_ind = indent_list[-1] # indentation of prev. line end_of_delim = -1 for pos, char in CharFilter(line): what_del_open = None what_del_close = None if pos > end_of_delim: [what_del_open, what_del_close] = get_curr_delim(line, pos) if what_del_open: what_del_open = what_del_open.group() end_of_delim = pos + len(what_del_open) - 1 level += 1 indent_list.append(pos + len(what_del_open) + rel_ind) pos_ldelim.append(pos) ldelim.append(what_del_open) if what_del_close: what_del_close = what_del_close.group() end_of_delim = pos + len(what_del_close) - 1 if level > 0: level += -1 indent_list.pop() else: log_message('unpaired bracket delimiters', "info", filename, line_nr) if pos_ldelim: pos_ldelim.pop() what_del_open = ldelim.pop() valid = False if what_del_open == r"(": valid = what_del_close == r")" if what_del_open == r"(/": valid = what_del_close == r"/)" if what_del_open == r"[": valid = what_del_close == r"]" if not valid: log_message('unpaired bracket delimiters', "info", filename, line_nr) else: pos_rdelim.append(pos) rdelim.append(what_del_close) if char == ',' and not level and pos_eq > 0: # a top level comma removes previous alignment position. # (see issue #11) pos_eq = 0 indent_list.pop() if not level and not is_decl and char == '=' and not REL_OP_RE.search( line[max(0, pos - 1):min(pos + 2, len(line))]): # should only have one assignment per line! if pos_eq > 0: raise FprettifyInternalException( "found more than one assignment in the same Fortran line", filename, line_nr) is_pointer = line[pos + 1] == '>' pos_eq = pos + 1 # don't align if assignment operator directly before # line break if not re.search(r"=>?\s*" + LINEBREAK_STR, line, RE_FLAGS): indent_list.append( pos_eq + 1 + is_pointer + indent_list[-1]) elif is_decl and line[pos:pos + 2] == '::' and not re.search(r"::\s*" + LINEBREAK_STR, line, RE_FLAGS): indent_list.append(pos + 3 + indent_list[-1]) elif is_use and line[pos] == ':' and not re.search(r":\s*" + LINEBREAK_STR, line, RE_FLAGS): indent_list.append(pos + 2 + indent_list[-1]) # Don't align if delimiter opening directly before line break if level and re.search(DEL_OPEN_STR + r"\s*" + LINEBREAK_STR, line, RE_FLAGS): if len(indent_list) > 1: indent_list[-1] = indent_list[-2] else: indent_list[-1] = 0 if not indent_list[-1]: indent_list[-1] = indent_size self._level = level def inspect_ffile_format(infile, indent_size, strict_indent, indent_fypp=False, orig_filename=None): """ Determine indentation by inspecting original Fortran file. This is mainly for finding aligned blocks of DO/IF statements. Also check if it has f77 constructs. :param infile: open file :param indent_size: the default indent size :orig_filename: filename used for messages :returns: [ target indent sizes for each line, indent of first line (offset) ] """ if not orig_filename: orig_filename = infile.name num_labels = False indents = [] stream = InputStream(infile, filter_fypp=not indent_fypp, orig_filename=orig_filename) prev_offset = 0 first_indent = -1 while 1: f_line, _, lines = stream.next_fortran_line() if not lines: break f_line, lines, label = preprocess_labels(f_line, lines) offset = len(lines[0]) - len(lines[0].lstrip(' ')) if f_line.strip() and first_indent == -1: first_indent = offset indents.append(offset - prev_offset) # don't impose indentation for blocked do/if constructs: if (IF_RE.search(f_line) or DO_RE.search(f_line)): if (prev_offset != offset or strict_indent): indents[-1] = indent_size else: indents[-1] = indent_size prev_offset = offset return indents, first_indent def replace_relational_single_fline(f_line, cstyle): """ format a single Fortran line - replaces scalar relational operators in logical expressions to either Fortran or C-style. .lt. <--> < .le. <--> <= .gt. <--> > .ge. <--> >= .eq. <--> == .ne. <--> /= """ new_line = f_line # only act on lines that do contain a relation if REL_OP_RE.search(f_line): # check that relation is not inside quotes, a string, or commented # (think of underlining a heading with === or things like markup being printed which we do not replace) pos_prev = -1 pos = -1 line_parts = [''] for pos, char in CharFilter(f_line): if pos > pos_prev + 1: # skipped string line_parts.append(f_line[pos_prev + 1:pos].strip()) # append string line_parts.append('') line_parts[-1] += char pos_prev = pos if pos + 1 < len(f_line): line_parts.append(f_line[pos + 1:]) for pos, part in enumerate(line_parts): # exclude comments, strings: if not STR_OPEN_RE.match(part): # also exclude / if we see a namelist and data statement if cstyle: part = re.sub(r"\.LT\.", "< ", part, flags=RE_FLAGS) part = re.sub(r"\.LE\.", "<= ", part, flags=RE_FLAGS) part = re.sub(r"\.GT\.", "> ", part, flags=RE_FLAGS) part = re.sub(r"\.GE\.", ">= ", part, flags=RE_FLAGS) part = re.sub(r"\.EQ\.", "== ", part, flags=RE_FLAGS) part = re.sub(r"\.NE\.", "/= ", part, flags=RE_FLAGS) else: part = re.sub(r"<=", ".le.", part, flags=RE_FLAGS) part = re.sub(r"<", ".lt.", part, flags=RE_FLAGS) part = re.sub(r">=", ".ge.", part, flags=RE_FLAGS) part = re.sub(r">", ".gt.", part, flags=RE_FLAGS) part = re.sub(r"==", ".eq.", part, flags=RE_FLAGS) part = re.sub(r"\/=", ".ne.", part, flags=RE_FLAGS) line_parts[pos] = part new_line = ''.join(line_parts) return new_line def replace_keywords_single_fline(f_line, case_dict): """ format a single Fortran line - change case of keywords """ new_line = f_line # Collect words list pos_prev = -1 pos = -1 line_parts = [''] for pos, char in CharFilter(f_line): if pos > pos_prev + 1: # skipped string line_parts.append(f_line[pos_prev + 1:pos].strip()) # append string line_parts.append('') line_parts[-1] += char pos_prev = pos if pos + 1 < len(f_line): line_parts.append(f_line[pos + 1:]) line_parts = [[a] if STR_OPEN_RE.match(a) else re.split(F90_OPERATORS_RE,a) for a in line_parts] # problem, split "." line_parts = [b for a in line_parts for b in a] ## line_parts = [[a] if STR_OPEN_RE.match(a) else re.split('(\W)',a) ## for a in line_parts] # problem, split "." line_parts = [[a] if STR_OPEN_RE.match(a) else re.split('([^a-zA-Z0-9_.])',a) for a in line_parts] line_parts = [b for a in line_parts for b in a] swapcase = lambda s, a: s if a==0 else (s.lower() if a==1 else s.upper()) nbparts = len(line_parts) for pos, part in enumerate(line_parts): # exclude comments, strings: if part.strip() and not STR_OPEN_RE.match(part): if F90_KEYWORDS_RE.match(part): part = swapcase(part, case_dict['keywords']) elif F90_MODULES_RE.match(part): part = swapcase(part, case_dict['procedures']) elif F90_PROCEDURES_RE.match(part): ok = False for pos2 in range(pos+1, nbparts): part2 = line_parts[pos2] if part2.strip() and not (part2 == '\n' or STR_OPEN_RE.match(part2)): ok = (part2 == '(') break if ok: part = swapcase(part, case_dict['procedures']) elif F90_OPERATORS_RE.match(part): part = swapcase(part, case_dict['operators']) elif F90_CONSTANTS_RE.match(part): part = swapcase(part, case_dict['constants']) elif F90_CONSTANTS_TYPES_RE.match(part): part = swapcase(part, case_dict['constants']) elif F90_NUMBER_ALL_REC.match(part): part = swapcase(part, case_dict['constants']) line_parts[pos] = part new_line = ''.join(line_parts) return new_line def format_single_fline(f_line, whitespace, whitespace_dict, linebreak_pos, ampersand_sep, scope_parser, format_decl, filename, line_nr, auto_format=True): """ format a single Fortran line - imposes white space formatting and inserts linebreaks. Takes a logical Fortran line `f_line` as input as well as the positions of the linebreaks (`linebreak_pos`), and the number of separating whitespace characters before ampersand (`ampersand_sep`). `filename` and `line_nr` just for error messages. The higher `whitespace`, the more white space characters inserted - whitespace = 0, 1, 2, 3 are currently supported. whitespace formatting can additionally controlled more fine-grained via a dictionary of bools (whitespace_dict) auto formatting can be turned off by setting `auto_format` to False. """ # define whether to put whitespaces around operators: mapping = { 'comma': 0, # 0: comma, semicolon 'assignments': 1, # 1: assignment operators 'relational': 2, # 2: relational operators 'logical': 3, # 3: logical operators 'plusminus': 4, # 4: arithm. operators plus and minus 'multdiv': 5, # 5: arithm. operators multiply and divide 'print': 6, # 6: print / read statements 'type': 7, # 7: select type components 'intrinsics': 8, # 8: intrinsics 'decl': 9 # 9: declarations } if whitespace == 0: spacey = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] elif whitespace == 1: spacey = [1, 1, 1, 1, 0, 0, 1, 0, 1, 1] elif whitespace == 2: spacey = [1, 1, 1, 1, 1, 0, 1, 0, 1, 1] elif whitespace == 3: spacey = [1, 1, 1, 1, 1, 1, 1, 0, 1, 1] elif whitespace == 4: spacey = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] else: raise NotImplementedError("unknown value for whitespace") if whitespace_dict: # iterate over dictionary and override settings for 'spacey' for key, value in mapping.items(): if whitespace_dict[key] == True: spacey[value] = 1 elif whitespace_dict[key] == False: spacey[value] = 0 line = f_line line_orig = line if auto_format: line = rm_extra_whitespace(line, format_decl) line = add_whitespace_charwise(line, spacey, scope_parser, format_decl, filename, line_nr) line = add_whitespace_context(line, spacey) lines_out = split_reformatted_line( line_orig, linebreak_pos, ampersand_sep, line, filename, line_nr) return lines_out def rm_extra_whitespace(line, format_decl): """rm all unneeded whitespace chars, except for declarations""" line_ftd = '' pos_prev = -1 pos = -1 for pos, char in CharFilter(line): if format_decl: is_decl = False else: is_decl = line[pos:].lstrip().startswith('::') or line[ :pos].rstrip().endswith('::') if pos > pos_prev + 1: # skipped string line_ftd = line_ftd + line[pos_prev + 1:pos] if char == ' ': # remove double spaces: if line_ftd and (re.search(r'[\w]', line_ftd[-1]) or is_decl): line_ftd = line_ftd + char else: if (line_ftd and line_ftd[-1] == ' ' and (not re.search(r'[\w]', char) and not is_decl)): line_ftd = line_ftd[:-1] # remove spaces except between words line_ftd = line_ftd + char pos_prev = pos line_ftd = line_ftd + line[pos+1:] return line_ftd def add_whitespace_charwise(line, spacey, scope_parser, format_decl, filename, line_nr): """add whitespace character wise (no need for context aware parsing)""" line_ftd = line pos_eq = [] end_of_delim = -1 level = 0 for pos, char in CharFilter(line): # offset w.r.t. unformatted line offset = len(line_ftd) - len(line) # format delimiters what_del_open = None what_del_close = None if pos > end_of_delim: [what_del_open, what_del_close] = get_curr_delim(line, pos) if what_del_open or what_del_close: sep1 = 0 sep2 = 0 if what_del_open: delim = what_del_open.group() else: delim = what_del_close.group() lhs = line_ftd[:pos + offset] rhs = line_ftd[pos + len(delim) + offset:] # format opening delimiters if what_del_open: level += 1 # new scope # add separating whitespace before opening delimiter # with some exceptions: # FIXME: duplication of regex, better to include them into # INTR_STMTS_PAR if ((not re.search((r"(" + DEL_OPEN_STR + r"|[\w\*/=\+\-:])\s*$"), line[:pos], RE_FLAGS) and not EMPTY_RE.search(line[:pos])) or re.search(SOL_STR + r"(\w+\s*:)?(ELSE)?\s*IF\s*$", line[:pos], RE_FLAGS) or re.search(SOL_STR + r"(\w+\s*:)?\s*DO\s+WHILE\s*$", line[:pos], RE_FLAGS) or re.search(SOL_STR + r"(SELECT)?\s*CASE\s*$", line[:pos], RE_FLAGS) or re.search(SOL_STR + r"(SELECT)?\s*RANK\s*$", line[:pos], RE_FLAGS) or re.search(SOL_STR + r"SELECT\s*TYPE\s*$", line[:pos], RE_FLAGS) or re.search(SOL_STR + r"CLASS\s*DEFAULT\s*$", line[:pos], RE_FLAGS) or re.search(SOL_STR + r"(TYPE|CLASS)\s+IS\s*$", line[:pos], RE_FLAGS) or re.search(r"(? 0: level += -1 # close scope else: log_message('unpaired bracket delimiters', "info", filename, line_nr) # add separating whitespace after closing delimiter # with some exceptions: if not re.search(r"^\s*(" + DEL_CLOSE_STR + r"|[,%:/\*])", line[pos + 1:], RE_FLAGS): sep2 = 1 elif re.search(r"^\s*::", line[pos + 1:], RE_FLAGS): sep2 = len(rhs) - len(rhs.lstrip(' ')) if not format_decl else 1 # where delimiter token ends end_of_delim = pos + len(delim) - 1 line_ftd = lhs.rstrip(' ') + ' ' * sep1 + \ delim + ' ' * sep2 + rhs.lstrip(' ') # format commas and semicolons if char in [',', ';']: lhs = line_ftd[:pos + offset] rhs = line_ftd[pos + 1 + offset:] line_ftd = lhs.rstrip(' ') + char + ' ' * \ spacey[0] + rhs.lstrip(' ') line_ftd = line_ftd.rstrip(' ') # format type selector % if char == "%": lhs = line_ftd[:pos + offset] rhs = line_ftd[pos + 1 + offset:] line_ftd = lhs.rstrip(' ') \ + ' ' * spacey[7] \ + char \ + ' ' * spacey[7] \ + rhs.lstrip(' ') line_ftd = line_ftd.rstrip(' ') # format '::' if format_decl and line[pos:pos+2] == "::": lhs = line_ftd[:pos + offset] rhs = line_ftd[pos + 2 + offset:] line_ftd = lhs.rstrip(' ') \ + ' ' * spacey[9] \ + '::' + ' ' * spacey[9] \ + rhs.lstrip(' ') line_ftd = line_ftd.rstrip(' ') # format .NOT. if re.search(r"^\.NOT\.", line[pos:pos + 5], RE_FLAGS): lhs = line_ftd[:pos + offset] rhs = line_ftd[pos + 5 + offset:] line_ftd = lhs.rstrip( ' ') + line[pos:pos + 5] + ' ' * spacey[3] + rhs.lstrip(' ') # strip whitespaces from '=' and prepare assignment operator # formatting: if char == '=' and not REL_OP_RE.search(line[pos - 1:pos + 2]): lhs = line_ftd[:pos + offset] rhs = line_ftd[pos + 1 + offset:] line_ftd = lhs.rstrip(' ') + '=' + rhs.lstrip(' ') is_pointer = line[pos + 1] == '>' if (not level) or is_pointer: # remember position of assignment operator pos_eq.append(len(lhs.rstrip(' '))) line = line_ftd for pos in pos_eq: offset = len(line_ftd) - len(line) is_pointer = line[pos + 1] == '>' lhs = line_ftd[:pos + offset] rhs = line_ftd[pos + 1 + is_pointer + offset:] if is_pointer: assign_op = '=>' # pointer assignment else: assign_op = '=' # assignment line_ftd = (lhs.rstrip(' ') + ' ' * spacey[1] + assign_op + ' ' * spacey[1] + rhs.lstrip(' ')) # offset w.r.t. unformatted line is_end = False if END_RE.search(line_ftd): for endre in scope_parser['end']: if endre and endre.search(line_ftd): is_end = True if is_end: line_ftd = END_RE.sub(r'\1' + ' '*spacey[8] + r'\2', line_ftd) if level != 0: log_message('unpaired bracket delimiters', "info", filename, line_nr) return line_ftd def add_whitespace_context(line, spacey): """ for context aware whitespace formatting we extract line parts that are not comments or strings in order to be able to apply a context aware regex. """ pos_prev = -1 pos = -1 line_parts = [''] for pos, char in CharFilter(line): if pos > pos_prev + 1: # skipped string line_parts.append(line[pos_prev + 1:pos].strip()) # append string line_parts.append('') line_parts[-1] += char pos_prev = pos if pos + 1 < len(line): line_parts.append(line[pos + 1:]) # format namelists with spaces around / if NML_STMT_RE.match(line): for pos, part in enumerate(line_parts): # exclude comments, strings: if not STR_OPEN_RE.match(part): partsplit = NML_RE.split(part) line_parts[pos] = (' '.join(partsplit)) # Two-sided operators for n_op, lr_re in enumerate(LR_OPS_RE): for pos, part in enumerate(line_parts): # exclude comments, strings: if not STR_OPEN_RE.match(part): # also exclude / if we see a namelist and data statement if not ( NML_STMT_RE.match(line) or DATA_STMT_RE.match(line) ): partsplit = lr_re.split(part) line_parts[pos] = (' ' * spacey[n_op + 2]).join(partsplit) line = ''.join(line_parts) for newre in [IF_RE, DO_RE, BLK_RE]: if newre.search(line) and re.search(SOL_STR + r"\w+\s*:", line): line = ': '.join(_.strip() for _ in line.split(':', 1)) # 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) return line def split_reformatted_line(line_orig, linebreak_pos_orig, ampersand_sep, line, filename, line_nr): """ Infer linebreak positions of formatted line from linebreak positions in original line and split line. """ # shift line break positions from original to reformatted line pos_new = 0 pos_old = 0 linebreak_pos_orig.sort(reverse=True) linebreak_pos_ftd = [] while 1: if pos_new == len(line) or pos_old == len(line_orig): break if line[pos_new] != line_orig[pos_old]: raise FprettifyInternalException( "failed at finding line break position", filename, line_nr) if linebreak_pos_orig and pos_old > linebreak_pos_orig[-1]: linebreak_pos_orig.pop() linebreak_pos_ftd.append(pos_new) continue pos_new += 1 while pos_new < len(line) and line[pos_new] == ' ': pos_new += 1 pos_old += 1 while pos_old < len(line_orig) and line_orig[pos_old] == ' ': pos_old += 1 linebreak_pos_ftd.insert(0, 0) # We split line into parts and we insert ampersands at line end, but not # for empty lines and comment lines lines_split = [(line[l:r].rstrip(' ') + ' ' * ampersand_sep[pos] + '&' * min(1, r - l)) for pos, (l, r) in enumerate(zip(linebreak_pos_ftd[0:-1], linebreak_pos_ftd[1:]))] lines_split.append(line[linebreak_pos_ftd[-1]:]) return lines_split def diff(a, b, a_name, b_name): # type: (str, str, str, str) -> str """Return a unified diff string between strings `a` and `b`.""" import difflib a_lines = [line + "\n" for line in a.splitlines()] b_lines = [line + "\n" for line in b.splitlines()] return "".join( difflib.unified_diff(a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5) ) def reformat_inplace(filename, stdout=False, diffonly=False, **kwargs): # pragma: no cover """reformat a file in place.""" if filename == '-': infile = io.StringIO() infile.write(sys.stdin.read()) else: infile = io.open(filename, 'r', encoding='utf-8') newfile = io.StringIO() reformat_ffile(infile, newfile, orig_filename=filename, **kwargs) if diffonly: infile.seek(0) newfile.seek(0) diff_contents=diff(infile.read(),newfile.read(),filename,filename) sys.stdout.write(diff_contents) else: if stdout: sys.stdout.write(newfile.getvalue()) else: outfile = io.open(filename, 'r', encoding='utf-8') # write to outfile only if content has changed import hashlib hash_new = hashlib.md5() hash_new.update(newfile.getvalue().encode('utf-8')) hash_old = hashlib.md5() hash_old.update(outfile.read().encode('utf-8')) outfile.close() if hash_new.digest() != hash_old.digest(): outfile = io.open(filename, 'w', encoding='utf-8') outfile.write(newfile.getvalue()) 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): """main method to be invoked for formatting a Fortran file.""" # note: whitespace formatting and indentation may require different parsing rules # (e.g. preprocessor statements may be indented but not whitespace formatted) # therefore we invoke reformat_ffile independently for: # 1) whitespace formatting # 2) indentation if not orig_filename: orig_filename = infile.name # 1) whitespace formatting oldfile = infile newfile = infile if impose_whitespace: _impose_indent = False newfile = io.StringIO() 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) oldfile = newfile # 2) indentation if impose_indent: _impose_whitespace = False _impose_replacements = False newfile = io.StringIO() 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) outfile.write(newfile.getvalue()) 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): if not orig_filename: orig_filename = infile.name if not impose_indent: indent_fypp = False scope_parser = build_scope_parser(fypp=indent_fypp, mod=indent_mod) infile.seek(0) req_indents, first_indent = inspect_ffile_format( infile, indent_size, strict_indent, indent_fypp, orig_filename) infile.seek(0) # initialization # special cases for indentation: # indent_special = 0: parse syntax and impose indent # indent_special = 1: no indentation # indent_special = 2: use indent from previous line # indent_special = 3: take indent from input file (leave as is) indent_special = 0 if impose_indent: indenter = F90Indenter(scope_parser, first_indent, indent_size, orig_filename) else: indent_special = 3 impose_case = not all(v == 0 for v in case_dict.values()) nfl = 0 # fortran line counter use_same_line = False stream = InputStream(infile, not indent_fypp, orig_filename=orig_filename) skip_blank = False in_format_off_block = False while 1: f_line, comments, lines = stream.next_fortran_line() if not lines: break nfl += 1 orig_lines = lines if indent_special != 3: indent = [0] * len(lines) else: indent = [len(l) - len((l.lstrip(' ')).lstrip('&')) for l in lines] comment_lines = format_comments(lines, comments, strip_comments) auto_align, auto_format, in_format_off_block = parse_fprettify_directives( lines, comment_lines, in_format_off_block, orig_filename, stream.line_nr) f_line, lines, is_omp_conditional = preprocess_omp( f_line, lines) f_line, lines, label = preprocess_labels(f_line, lines) lines, do_format, prev_indent, is_blank, is_special = preprocess_line( f_line, lines, comments, orig_filename, stream.line_nr, indent_fypp) if is_special[0]: indent_special = 3 if prev_indent and indent_special == 0: indent_special = 2 if is_blank and skip_blank: continue if (not do_format): if indent_special == 2: # inherit indent from previous line indent[:] = [indenter.get_fline_indent()]*len(indent) elif indent_special == 0: indent_special = 1 else: if not auto_align: manual_lines_indent = get_manual_alignment(lines) else: manual_lines_indent = [] lines, pre_ampersand, ampersand_sep = remove_pre_ampersands( lines, is_special, orig_filename, stream.line_nr) linebreak_pos = get_linebreak_pos(lines, filter_fypp=not indent_fypp) f_line = f_line.strip(' ') if impose_replacements: f_line = replace_relational_single_fline(f_line, cstyle) if impose_case: f_line = replace_keywords_single_fline(f_line, case_dict) if impose_whitespace: lines = format_single_fline( f_line, whitespace, whitespace_dict, linebreak_pos, ampersand_sep, scope_parser, format_decl, orig_filename, stream.line_nr, auto_format) lines = append_comments(lines, comment_lines, is_special) # target indent for next line rel_indent = req_indents[nfl] if nfl < len(req_indents) else 0 if indent_special != 3: indenter.process_lines_of_fline( f_line, lines, rel_indent, indent_size, stream.line_nr, indent_fypp, manual_lines_indent) indent = indenter.get_lines_indent() lines, indent = prepend_ampersands(lines, indent, pre_ampersand) if any(is_special): for pos, line in enumerate(lines): if is_special[pos]: indent[pos] = len(line) - len(line.lstrip(' ')) lines[pos] = line.lstrip(' ') lines = remove_trailing_whitespace(lines) write_formatted_line(outfile, indent, lines, orig_lines, indent_special, llength, use_same_line, is_omp_conditional, label, orig_filename, stream.line_nr) do_indent, use_same_line = pass_defaults_to_next_line(f_line) if impose_indent: if do_indent: indent_special = 0 else: indent_special = 1 # rm subsequent blank lines skip_blank = EMPTY_RE.search( f_line) and not any(comments) and not is_omp_conditional and not label def format_comments(lines, comments, strip_comments): comments_ftd = [] for line, comment in zip(lines, comments): has_comment = bool(comment.strip()) if has_comment: if strip_comments: sep = not comment.strip() == line.strip() else: line_minus_comment = line.replace(comment,"") sep = len(line_minus_comment.rstrip('\n')) - len(line_minus_comment.rstrip()) else: sep = 0 if line.strip(): # empty lines between linebreaks are ignored comments_ftd.append(' ' * sep + comment.strip()) return comments_ftd def parse_fprettify_directives(lines, comment_lines, in_format_off_block, filename, line_nr): """ parse formatter directives '!&' and line continuations starting with an ampersand. """ auto_align = not any(NO_ALIGN_RE.search(_) for _ in lines) auto_format = not (in_format_off_block or any( _.lstrip().startswith('!&') for _ in comment_lines)) if not auto_format: auto_align = False if (len(lines)) == 1: valid_directive = True if lines[0].strip().startswith('!&<'): if in_format_off_block: valid_directive = False else: in_format_off_block = True if lines[0].strip().startswith('!&>'): if not in_format_off_block: valid_directive = False else: in_format_off_block = False if not valid_directive: raise FprettifyParseException( FORMATTER_ERROR_MESSAGE, filename, line_nr) return [auto_align, auto_format, in_format_off_block] def preprocess_omp(f_line, lines): """convert omp conditional to normal fortran""" is_omp_conditional = bool(OMP_COND_RE.search(f_line)) if is_omp_conditional: f_line = OMP_COND_RE.sub(' ', f_line, count=1) lines = [OMP_COND_RE.sub(' ', l, count=1) for l in lines] return [f_line, lines, is_omp_conditional] def preprocess_labels(f_line, lines): """remove statement labels""" match = STATEMENT_LABEL_RE.search(f_line) if match: label = match.group(1) else: label = '' if label: f_line = STATEMENT_LABEL_RE.sub(len(label)*' ', f_line, count=1) lines[0] = STATEMENT_LABEL_RE.sub(len(label)*' ', lines[0], count=1) return [f_line, lines, label] def preprocess_line(f_line, lines, comments, filename, line_nr, indent_fypp): """preprocess lines: identification and formatting of special cases""" is_blank = False prev_indent = False do_format = False # is_special: special directives that should not be treated as Fortran # currently supported: fypp preprocessor directives or comments for FORD documentation is_special = [False]*len(lines) for pos, line in enumerate(lines): line_strip = line.lstrip() if indent_fypp: is_special[pos] = line_strip.startswith('!!') or \ (FYPP_LINE_RE.search(line_strip) if pos > 0 else False) else: is_special[pos] = FYPP_LINE_RE.search(line_strip) or line_strip.startswith('!!') # if first line is special, all lines should be special if is_special[0]: is_special = [True]*len(lines) if EMPTY_RE.search(f_line): # empty lines including comment lines if any(comments): if lines[0].startswith(' '): # indent comment lines only if they were not indented before. prev_indent = True else: is_blank = True lines = [l.strip(' ') if not is_special[n] else l for n, l in enumerate(lines)] else: do_format = True return [lines, do_format, prev_indent, is_blank, is_special] def pass_defaults_to_next_line(f_line): """defaults to be transferred from f_line to next line""" if re.search(r";\s*$", f_line, RE_FLAGS): # if line ended with semicolon, don't indent next line do_indent = False use_same_line = True else: do_indent = True use_same_line = False return [do_indent, use_same_line] def remove_trailing_whitespace(lines): """remove trailing whitespaces from lines""" lines = [re.sub(r"\s+$", '\n', l, RE_FLAGS) for l in lines] return lines def prepend_ampersands(lines, indent, pre_ampersand): """prepend ampersands and correct indent""" for pos, line in enumerate(lines): amp_insert = pre_ampersand[pos] if amp_insert: indent[pos] += -1 lines[pos] = amp_insert + line.lstrip() return [lines, indent] def append_comments(lines, comment_lines, is_special): """append comments to lines""" for pos, (line, comment) in enumerate(zip(lines, comment_lines)): if pos < len(lines) - 1: has_nl = True # has next line if not line.strip() and not is_special[pos]: comment = comment.lstrip() else: has_nl = not re.search(EOL_SC, line) lines[pos] = lines[pos].rstrip(' ') + comment + '\n' * has_nl return lines def get_linebreak_pos(lines, filter_fypp=True): """extract linebreak positions in Fortran line from lines""" linebreak_pos = [] if filter_fypp: notfortran_re = NOTFORTRAN_LINE_RE else: notfortran_re = NOTFORTRAN_FYPP_LINE_RE for line in lines: found = None for char_pos, _ in CharFilter(line, filter_strings=False): if re.match(LINEBREAK_STR, line[char_pos:], RE_FLAGS): found = char_pos if found: linebreak_pos.append(found) elif notfortran_re.search(line.lstrip(' ')): linebreak_pos.append(0) linebreak_pos = [sum(linebreak_pos[0:_ + 1]) - 1 for _ in range(0, len(linebreak_pos))] return linebreak_pos def remove_pre_ampersands(lines, is_special, filename, line_nr): """ remove and return preceding ampersands ('pre_ampersand'). Also return number of whitespace characters before ampersand of previous line ('ampersand_sep'). Note: Don't do any whitespace formatting on ampersands if next line starts with an ampersand but remember the original number of spaces (ampersand_sep). This "special rule" is necessary since ampersands starting a line can be used to break literals, so changing the number of whitespaces before the ampersand ending the previous line may lead to invalid syntax or may change the number of whitespace characters in a string. """ pre_ampersand = [] ampersand_sep = [] for pos, line in enumerate(lines): match = re.search(SOL_STR + r'(&\s*)', line) if match: pre_ampersand.append(match.group(1)) # amount of whitespace before ampersand of previous line: m = re.search(r'(\s*)&[\s]*(?:!.*)?$', lines[pos - 1]) if not m: raise FprettifyParseException( "Bad continuation line format", filename, line_nr) sep = len(m.group(1)) ampersand_sep.append(sep) else: pre_ampersand.append('') if pos > 0: # use default 1 whitespace character before ampersand ampersand_sep.append(1) lines = [l.strip(' ').strip('&') if not s else l for l, s in zip(lines, is_special)] return [lines, pre_ampersand, ampersand_sep] def get_manual_alignment(lines): """extract manual indents for line continuations from line""" manual_lines_indent = [ len(l) - len(l.lstrip(' ').lstrip('&')) for l in lines] manual_lines_indent = [ind - manual_lines_indent[0] for ind in manual_lines_indent] return manual_lines_indent def write_formatted_line(outfile, indent, lines, orig_lines, indent_special, llength, use_same_line, is_omp_conditional, label, filename, line_nr): """Write reformatted line to file""" for ind, line, orig_line in zip(indent, lines, orig_lines): # get actual line length excluding comment: line_length = 0 for line_length, _ in CharFilter(line): pass line_length += 1 if indent_special != 1: ind_use = ind else: if use_same_line: ind_use = 1 else: ind_use = 0 if CPP_RE.search(line.lstrip()): ind_use = 0 if label: label_use = label + ' ' label = '' # no label for continuation lines else: label_use = '' if ind_use + line_length <= (llength+1): # llength (default 132) plus 1 newline char outfile.write('!$ ' * is_omp_conditional + label_use + ' ' * (ind_use - 3 * is_omp_conditional - len(label_use) + len(line) - len(line.lstrip(' '))) + line.lstrip(' ')) elif line_length <= (llength+1): outfile.write('!$ ' * is_omp_conditional + label_use + ' ' * ((llength+1) - 3 * is_omp_conditional - len(label_use) - len(line.lstrip(' '))) + line.lstrip(' ')) log_message(LINESPLIT_MESSAGE+" (limit: "+str(llength)+")", "warning", filename, line_nr) else: outfile.write(orig_line) log_message(LINESPLIT_MESSAGE+" (limit: "+str(llength)+")", "warning", filename, line_nr) def get_curr_delim(line, pos): """get delimiter token in line starting at pos, if it exists""" what_del_open = DEL_OPEN_RE.search(line[pos:pos + 2]) what_del_close = DEL_CLOSE_RE.search(line[pos:pos + 2]) return [what_del_open, what_del_close] def set_fprettify_logger(level): """setup custom logger""" logger = logging.getLogger('fprettify-logger') logger.setLevel(level) stream_handler = logging.StreamHandler() stream_handler.setLevel(level) formatter = logging.Formatter( '%(levelname)s: File %(ffilename)s, line %(fline)s\n %(message)s') stream_handler.setFormatter(formatter) logger.addHandler(stream_handler) def log_exception(e, message): """log an exception and a message""" log_message(message, "exception", e.filename, e.line_nr) def log_message(message, level, filename, line_nr): """log a message""" logger = logging.getLogger('fprettify-logger') logger_d = {'ffilename': filename, 'fline': line_nr} logger_to_use = getattr(logger, level) logger_to_use(message, extra=logger_d) def run(argv=sys.argv): # pragma: no cover """Command line interface""" try: import configargparse as argparse except ImportError: import argparse def str2bool(str): """helper function to convert strings to bool""" if str.lower() in ('yes', 'true', 't', 'y', '1'): return True elif str.lower() in ('no', 'false', 'f', 'n', '0'): return False else: return None def get_config_file_list(filename): """helper function to create list of config files found in parent directories""" config_file_list = [] dir = os.path.dirname(filename) while True: config_file = os.path.join(dir, '.fprettify.rc') if os.path.isfile(config_file): config_file_list.insert(0, config_file) parent=os.path.dirname(dir) if parent == dir: break dir = parent return config_file_list arguments = {'prog': argv[0], 'description': 'Auto-format modern Fortran source files.', 'formatter_class': argparse.ArgumentDefaultsHelpFormatter} if argparse.__name__ == "configargparse": arguments['args_for_setting_config_path'] = ['-c', '--config-file'] arguments['description'] = arguments['description'] + " Config files ('.fprettify.rc') in the home (~) directory and any such files located in parent directories of the input file will be used. When the standard input is used, the search is started from the current directory." def get_arg_parser(args): """helper function to create the parser object""" parser = argparse.ArgumentParser(**args) parser.add_argument("-i", "--indent", type=int, default=3, help="relative indentation width") parser.add_argument("-l", "--line-length", type=int, default=132, help="column after which a line should end, viz. -ffree-line-length-n for GCC") parser.add_argument("-w", "--whitespace", type=int, choices=range(0, 5), default=2, help="Presets for the amount of whitespace - " " 0: minimal whitespace" " | 1: operators (except arithmetic), print/read" " | 2: operators, print/read, plus/minus" " | 3: operators, print/read, plus/minus, muliply/divide" " | 4: operators, print/read, plus/minus, muliply/divide, type component selector") parser.add_argument("--whitespace-comma", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for comma/semicolons") parser.add_argument("--whitespace-assignment", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for assignments") parser.add_argument("--whitespace-decl", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for declarations (requires '--enable-decl')") parser.add_argument("--whitespace-relational", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for relational operators") parser.add_argument("--whitespace-logical", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for logical operators") parser.add_argument("--whitespace-plusminus", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for plus/minus arithmetic") parser.add_argument("--whitespace-multdiv", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for multiply/divide arithmetic") parser.add_argument("--whitespace-print", type=str2bool, nargs="?", default="None", const=True, help="boolean, en-/disable whitespace for print/read statements") parser.add_argument("--whitespace-type", type=str2bool, nargs="?", default="None", const=True, 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("--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") parser.add_argument("--disable-whitespace", action='store_true', default=False, help="don't impose whitespace formatting") parser.add_argument("--enable-replacements", action='store_true', default=False, help="replace relational operators (e.g. '.lt.' <--> '<')") parser.add_argument("--c-relations", action='store_true', default=False, help="C-style relational operators ('<', '<=', ...)") parser.add_argument("--case", nargs=4, default=[0,0,0,0], type=int, help="Enable letter case formatting of intrinsics by specifying which of " "keywords, procedures/modules, operators and constants (in this order) should be lowercased or uppercased - " " 0: do nothing" " | 1: lowercase" " | 2: uppercase") parser.add_argument("--strip-comments", action='store_true', default=False, help="strip whitespaces before comments") parser.add_argument('--disable-fypp', action='store_true', default=False, help="Disables the indentation of fypp preprocessor blocks.") parser.add_argument('--disable-indent-mod', action='store_true', default=False, help="Disables the indentation after module / program.") 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, help="Write to stdout instead of formatting inplace") group = parser.add_mutually_exclusive_group() group.add_argument("-S", "--silent", "--no-report-errors", action='store_true', default=False, help="Don't write any errors or warnings to stderr") group.add_argument("-D", "--debug", action='store_true', default=False, help=argparse.SUPPRESS) parser.add_argument("path", type=str, nargs='*', help="Paths to files to be formatted inplace. If no paths are given, stdin (-) is used by default. Path can be a directory if --recursive is used.", default=['-']) parser.add_argument('-r', '--recursive', action='store_true', default=False, help="Recursively auto-format all Fortran files in subdirectories of specified path; recognized filename extensions: {}". format(", ".join(FORTRAN_EXTENSIONS))) parser.add_argument('-e', '--exclude', action='append', default=[], type=str, help="File or directory patterns to be excluded when searching for Fortran files to format") parser.add_argument('-f', '--fortran', type=str, action='append', default=[], help="Overrides default fortran extensions recognized by --recursive. Repeat this option to specify more than one extension.") parser.add_argument('--version', action='version', version='%(prog)s 0.3.7') return parser parser = get_arg_parser(arguments) args = parser.parse_args(argv[1:]) def build_ws_dict(args): """helper function to build whitespace dictionary""" ws_dict = {} ws_dict['comma'] = args.whitespace_comma ws_dict['assignments'] = args.whitespace_assignment ws_dict['decl'] = args.whitespace_decl ws_dict['relational'] = args.whitespace_relational ws_dict['logical'] = args.whitespace_logical ws_dict['plusminus'] = args.whitespace_plusminus ws_dict['multdiv'] = args.whitespace_multdiv ws_dict['print'] = args.whitespace_print ws_dict['type'] = args.whitespace_type ws_dict['intrinsics'] = args.whitespace_intrinsics return ws_dict case_dict = { 'keywords' : args.case[0], 'procedures' : args.case[1], 'operators' : args.case[2], 'constants' : args.case[3] } # support legacy input: if 'stdin' in args.path and not os.path.isfile('stdin'): args.path = ['-' if _ == 'stdin' else _ for _ in args.path] for directory in args.path: if directory == '-': if args.recursive: sys.stderr.write("--recursive requires a directory.\n") sys.exit() else: if not os.path.exists(directory): sys.stderr.write("directory " + directory + " does not exist!\n") sys.exit() if not os.path.isfile(directory) and directory != '-' and not args.recursive: sys.stderr.write("file " + directory + " does not exist!\n") sys.exit() if not args.recursive: filenames = [directory] else: if args.fortran: ext = args.fortran else: ext = FORTRAN_EXTENSIONS filenames = [] from fnmatch import fnmatch for dirpath, dirnames, files in os.walk(directory,topdown=True): # Prune excluded patterns from list of child directories dirnames[:] = [dirname for dirname in dirnames if not any( [fnmatch(dirname,exclude_pattern) or fnmatch(os.path.join(dirpath,dirname),exclude_pattern) for exclude_pattern in args.exclude] )] for ffile in [os.path.join(dirpath, f) for f in files if any(f.endswith(_) for _ in ext) and not any([ fnmatch(f,exclude_pattern) for exclude_pattern in args.exclude])]: filenames.append(ffile) for filename in filenames: # reparse arguments using the file's list of config files filearguments = arguments if argparse.__name__ == "configargparse": filearguments['default_config_files'] = ['~/.fprettify.rc'] + get_config_file_list(os.path.abspath(filename) if filename != '-' else os.getcwd()) file_argparser = get_arg_parser(filearguments) file_args = file_argparser.parse_args(argv[1:]) ws_dict = build_ws_dict(file_args) stdout = file_args.stdout or directory == '-' diffonly=file_args.diff if file_args.debug: level = logging.DEBUG elif args.silent: level = logging.CRITICAL else: level = logging.WARNING set_fprettify_logger(level) try: reformat_inplace(filename, stdout=stdout, diffonly=diffonly, impose_indent=not file_args.disable_indent, indent_size=file_args.indent, strict_indent=file_args.strict_indent, impose_whitespace=not file_args.disable_whitespace, impose_replacements=file_args.enable_replacements, cstyle=file_args.c_relations, case_dict=case_dict, whitespace=file_args.whitespace, whitespace_dict=ws_dict, llength=1024 if file_args.line_length == 0 else file_args.line_length, 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) except FprettifyException as e: log_exception(e, "Fatal error occured")