Skip to content

Commit 8881172

Browse files
committed
FIX: Respect exclude option in config file
I also added a bunch of tests to test this functionality. The new tests also cover the fix for #94.
1 parent bc78873 commit 8881172

File tree

2 files changed

+212
-34
lines changed

2 files changed

+212
-34
lines changed

fprettify/__init__.py

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,13 +1943,15 @@ def get_config_file_list(start_dir):
19431943
dir = parent
19441944
return config_file_list
19451945

1946-
arguments = {'prog': argv[0],
1946+
def get_argparse_arguments():
1947+
arguments = {'prog': argv[0],
19471948
'description': 'Auto-format modern Fortran source files.',
19481949
'formatter_class': argparse.ArgumentDefaultsHelpFormatter}
19491950

1950-
if argparse.__name__ == "configargparse":
1951-
arguments['args_for_setting_config_path'] = ['-c', '--config-file']
1952-
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."
1951+
if argparse.__name__ == "configargparse":
1952+
arguments['args_for_setting_config_path'] = ['-c', '--config-file']
1953+
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."
1954+
return arguments
19531955

19541956
def get_arg_parser(args):
19551957
"""helper function to create the parser object"""
@@ -2027,9 +2029,17 @@ def get_arg_parser(args):
20272029
version='%(prog)s 0.3.7')
20282030
return parser
20292031

2030-
parser = get_arg_parser(arguments)
2031-
2032-
args = parser.parse_args(argv[1:])
2032+
def pars_args_with_config_file(directory):
2033+
"""
2034+
Parse arguments together with the config file.
2035+
Requires configargparse package.
2036+
"""
2037+
filearguments = get_argparse_arguments()
2038+
filearguments['default_config_files'] = ['~/.fprettify.rc'] \
2039+
+ get_config_file_list(directory if directory != '-' else os.getcwd())
2040+
file_argparser = get_arg_parser(filearguments)
2041+
file_args = file_argparser.parse_args(argv[1:])
2042+
return file_args
20332043

20342044
def build_ws_dict(args):
20352045
"""helper function to build whitespace dictionary"""
@@ -2046,6 +2056,19 @@ def build_ws_dict(args):
20462056
ws_dict['intrinsics'] = args.whitespace_intrinsics
20472057
return ws_dict
20482058

2059+
def build_case_dict(args):
2060+
"""helper function to build case dictionary"""
2061+
return {
2062+
'keywords' : file_args.case[0],
2063+
'procedures' : file_args.case[1],
2064+
'operators' : file_args.case[2],
2065+
'constants' : file_args.case[3]
2066+
}
2067+
2068+
parser = get_arg_parser(get_argparse_arguments())
2069+
2070+
args = parser.parse_args(argv[1:])
2071+
20492072
# support legacy input:
20502073
if 'stdin' in args.path and not os.path.isfile('stdin'):
20512074
args.path = ['-' if _ == 'stdin' else _ for _ in args.path]
@@ -2057,13 +2080,14 @@ def build_ws_dict(args):
20572080
sys.exit(1)
20582081
else:
20592082
if not os.path.exists(directory):
2060-
sys.stderr.write("directory " + directory +
2083+
sys.stderr.write("file " + directory +
20612084
" does not exist!\n")
20622085
sys.exit(1)
2063-
if not os.path.isfile(directory) and directory != '-' and not args.recursive:
2064-
sys.stderr.write("file " + directory + " does not exist!\n")
2086+
if os.path.isdir(directory) and not args.recursive:
2087+
sys.stderr.write("%s is a directory. Use --recursive option\n" % directory)
20652088
sys.exit(1)
20662089

2090+
20672091
if not args.recursive:
20682092
filenames = [directory]
20692093
else:
@@ -2077,36 +2101,34 @@ def build_ws_dict(args):
20772101

20782102
for dirpath, dirnames, files in os.walk(directory,topdown=True):
20792103

2104+
file_args = args
2105+
if argparse.__name__ == "configargparse":
2106+
file_args = pars_args_with_config_file(directory)
2107+
20802108
# Prune excluded patterns from list of child directories
2109+
# https://stackoverflow.com/a/19859907
20812110
dirnames[:] = [dirname for dirname in dirnames if not any(
2082-
[fnmatch(dirname,exclude_pattern) or fnmatch(os.path.join(dirpath,dirname),exclude_pattern)
2083-
for exclude_pattern in args.exclude]
2111+
fnmatch(dirname,exclude_pattern) or fnmatch(os.path.join(dirpath,dirname),exclude_pattern)
2112+
for exclude_pattern in file_args.exclude
20842113
)]
20852114

20862115
for ffile in [os.path.join(dirpath, f) for f in files
20872116
if any(f.endswith(_) for _ in ext)
2088-
and not any([
2117+
and not any(
20892118
fnmatch(f,exclude_pattern)
2090-
for exclude_pattern in args.exclude])]:
2119+
for exclude_pattern in file_args.exclude)]:
20912120
filenames.append(ffile)
20922121

20932122
for filename in filenames:
20942123

20952124
# reparse arguments using the file's list of config files
2096-
filearguments = arguments
2125+
file_args = args
20972126
if argparse.__name__ == "configargparse":
2098-
filearguments['default_config_files'] = ['~/.fprettify.rc'] \
2099-
+ get_config_file_list(os.path.dirname(os.path.abspath(filename)) if filename != '-' else os.getcwd())
2100-
file_argparser = get_arg_parser(filearguments)
2101-
file_args = file_argparser.parse_args(argv[1:])
2102-
ws_dict = build_ws_dict(file_args)
2127+
dirname = os.path.dirname(os.path.abspath(filename))
2128+
file_args = pars_args_with_config_file(dirname)
21032129

2104-
case_dict = {
2105-
'keywords' : file_args.case[0],
2106-
'procedures' : file_args.case[1],
2107-
'operators' : file_args.case[2],
2108-
'constants' : file_args.case[3]
2109-
}
2130+
ws_dict = build_ws_dict(file_args)
2131+
case_dict = build_case_dict(file_args)
21102132

21112133
stdout = file_args.stdout or directory == '-'
21122134
diffonly=file_args.diff

fprettify/tests/__init__.py

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ def joinpath(path1, path2):
5252
RESULT_FILE = joinpath(RESULT_DIR, r'expected_results')
5353
FAILED_FILE = joinpath(RESULT_DIR, r'failed_results')
5454

55+
TEMP_DIR = joinpath(MYPATH, r'tmp_test_dir/')
56+
5557
RUNSCRIPT = joinpath(MYPATH, r"../../fprettify.py")
5658

5759
fprettify.set_fprettify_logger(logging.ERROR)
5860

5961

60-
class AlienInvasion(Exception):
62+
class FileException(Exception):
6163
"""Should not happen"""
6264
pass
6365

@@ -85,6 +87,27 @@ def setUp(self):
8587
"""
8688
self.maxDiff = None
8789

90+
def createTmpDir(self):
91+
"""
92+
Create a temporary directory for IO tests
93+
"""
94+
if os.path.lexists(TEMP_DIR):
95+
raise FileException(
96+
"remove directory %s" % TEMP_DIR) # pragma: no cover
97+
os.mkdir(TEMP_DIR)
98+
99+
def removeTmpDir(self):
100+
"""
101+
Remove the temporary test directory and all its content.
102+
"""
103+
if not os.path.isdir(TEMP_DIR):
104+
return
105+
106+
for dirpath, _, files in os.walk(TEMP_DIR, topdown=False):
107+
for f in files:
108+
os.remove(joinpath(dirpath, f))
109+
os.rmdir(dirpath)
110+
88111
@classmethod
89112
def setUpClass(cls):
90113
"""
@@ -258,10 +281,8 @@ def test_io(self):
258281
instring = "CALL alien_invasion( 👽 )"
259282
outstring_exp = "CALL alien_invasion(👽)"
260283

261-
alien_file = "alien_invasion.f90"
262-
if os.path.isfile(alien_file):
263-
raise AlienInvasion(
264-
"remove file alien_invasion.f90") # pragma: no cover
284+
self.createTmpDir()
285+
alien_file = joinpath(TEMP_DIR, "alien_invasion.f90")
265286

266287
try:
267288
with io.open(alien_file, 'w', encoding='utf-8') as infile:
@@ -289,11 +310,146 @@ def test_io(self):
289310
for outstr in outstring:
290311
self.assertEqual(outstring_exp, outstr.strip())
291312
except: # pragma: no cover
292-
if os.path.isfile(alien_file):
293-
os.remove(alien_file)
313+
self.removeTmpDir()
314+
raise
315+
else:
316+
self.removeTmpDir()
317+
318+
def test_recursive_mode(self):
319+
"""test recursive mode which finds all fortran files in the tree"""
320+
321+
instring = "CALL alien_invasion( x)"
322+
formatted_string = "CALL alien_invasion(x)"
323+
324+
self.createTmpDir()
325+
326+
# We will create the following paths inside TEMP_DIR
327+
# - alien_file.f90
328+
# - excluded_alien_file.f90
329+
# - subdir/alien_invasion.f90
330+
# - subdir/excluded_alien_invasion.f90
331+
# - excluded_subdir/alien_invasion.f90
332+
alien_file = "alien_invasion.f90"
333+
excluded_file = "excluded_alien_invasion.f90"
334+
subdir = joinpath(TEMP_DIR, "subdir/")
335+
excluded_subdir = joinpath(TEMP_DIR, "excluded_subdir/")
336+
os.mkdir(subdir)
337+
os.mkdir(excluded_subdir)
338+
339+
def create_file(fname):
340+
with io.open(fname, 'w', encoding='utf-8') as infile:
341+
infile.write(instring)
342+
343+
def check_output_file(fname, str_exp):
344+
with io.open(fname, 'r', encoding='utf-8') as infile:
345+
self.assertEqual(str_exp, infile.read().strip())
346+
347+
try:
348+
create_file(joinpath(TEMP_DIR, alien_file))
349+
create_file(joinpath(TEMP_DIR, excluded_file))
350+
create_file(joinpath(subdir, alien_file))
351+
create_file(joinpath(subdir, excluded_file))
352+
create_file(joinpath(excluded_subdir, alien_file))
353+
354+
p1 = subprocess.Popen([
355+
RUNSCRIPT,
356+
'--recursive',
357+
'-e', 'excluded_*',
358+
TEMP_DIR],
359+
stdout=subprocess.PIPE)
360+
p1.wait()
361+
362+
# Check files that should be formatted.
363+
check_output_file(joinpath(TEMP_DIR, alien_file), formatted_string)
364+
check_output_file(joinpath(subdir, alien_file), formatted_string)
365+
366+
# Check excluded files.
367+
check_output_file(joinpath(TEMP_DIR, excluded_file), instring)
368+
check_output_file(joinpath(subdir, excluded_file), instring)
369+
370+
# Check excluded directory.
371+
check_output_file(joinpath(excluded_subdir, alien_file), instring)
372+
373+
except: # pragma: no cover
374+
self.removeTmpDir()
375+
raise
376+
else:
377+
self.removeTmpDir()
378+
379+
def test_config_file(self):
380+
"""simple test for configuration file reading"""
381+
382+
outstring = []
383+
instring = "CALL alien_invasion( x)"
384+
outstring_with_config = "call alien_invasion(x)"
385+
outstring_without_config = "CALL alien_invasion(x)"
386+
387+
self.createTmpDir()
388+
dirname = TEMP_DIR
389+
390+
alien_file = joinpath(dirname, "alien_invasion.f90")
391+
excluded_file = joinpath(dirname, "excluded.f90")
392+
config_file = joinpath(dirname, ".fprettify.rc")
393+
conf_string = "case=[1,1,1,2]\nexclude=[excluded*]"
394+
395+
excluded_subdir = joinpath(TEMP_DIR, 'excluded_subdir/')
396+
subdir = joinpath(TEMP_DIR, 'subdir/')
397+
398+
def create_file(fname, string):
399+
with io.open(fname, 'w', encoding='utf-8') as infile:
400+
infile.write(string)
401+
402+
def check_output_file(fname, str_exp):
403+
with io.open(fname, 'r', encoding='utf-8') as infile:
404+
self.assertEqual(str_exp, infile.read().strip())
405+
406+
try:
407+
create_file(alien_file, instring)
408+
create_file(excluded_file, instring)
409+
create_file(config_file, conf_string)
410+
411+
# testing stdin --> stdout
412+
# In this case, the config file will not be read,
413+
# because it is not located in CWD.
414+
p1 = subprocess.Popen(RUNSCRIPT,
415+
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
416+
outstr = p1.communicate(instring.encode('UTF-8'))[0].decode('UTF-8')
417+
self.assertEqual(outstring_without_config, outstr.strip())
418+
419+
# testing file --> stdout
420+
p1 = subprocess.Popen([RUNSCRIPT, alien_file, '--stdout'],
421+
stdout=subprocess.PIPE)
422+
outstr = p1.communicate(instring.encode('UTF-8')[0])[0].decode('UTF-8')
423+
self.assertEqual(outstring_with_config, outstr.strip())
424+
425+
# testing recursive mode
426+
os.mkdir(subdir)
427+
file_in_subdir = joinpath(subdir, 'aliens.F90')
428+
create_file(file_in_subdir, instring)
429+
config_file_in_subdir = joinpath(subdir, ".fprettify.rc")
430+
# Config file in subdir should take precedence.
431+
create_file(config_file_in_subdir, "case=[2,2,2,2]")
432+
433+
os.mkdir(excluded_subdir)
434+
file_in_excluded_subdir = joinpath(excluded_subdir, 'aliens.F90')
435+
create_file(file_in_excluded_subdir, instring)
436+
437+
p1 = subprocess.Popen([RUNSCRIPT, '--recursive', dirname],
438+
stdout=subprocess.PIPE)
439+
p1.wait()
440+
441+
check_output_file(alien_file, outstring_with_config)
442+
# Excluded files and directories should not be touched at all.
443+
check_output_file(excluded_file, instring)
444+
check_output_file(file_in_excluded_subdir, instring)
445+
# subdir contains a different config file which should take precedence.
446+
check_output_file(file_in_subdir, outstring_without_config)
447+
448+
except: # pragma: no cover
449+
self.removeTmpDir()
294450
raise
295451
else:
296-
os.remove(alien_file)
452+
self.removeTmpDir()
297453

298454
def test_multi_alias(self):
299455
"""test for issue #11 (multiple alias and alignment)"""

0 commit comments

Comments
 (0)