Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit 6e3d7dd

Browse files
committed
Merge pull request #87 from Nurdok/config-file
Added config file handling.
2 parents 08a0869 + da7da91 commit 6e3d7dd

File tree

3 files changed

+160
-10
lines changed

3 files changed

+160
-10
lines changed

pep257.py

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020
from itertools import takewhile, dropwhile, chain
2121
from optparse import OptionParser
2222
from re import compile as re
23+
try: # Python 3.x
24+
from ConfigParser import RawConfigParser
25+
except ImportError: # Python 2.x
26+
from configparser import RawConfigParser
2327

2428
log = logging.getLogger()
25-
log.addHandler(logging.StreamHandler())
29+
log.setLevel(logging.DEBUG)
2630

2731

2832
try:
@@ -49,6 +53,7 @@ def next(obj, default=nothing):
4953
__version__ = '0.3.3-alpha'
5054
__all__ = ('check', 'collect')
5155

56+
PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep257')
5257

5358
humanize = lambda string: re(r'(.)([A-Z]+)').sub(r'\1 \2', string).lower()
5459
is_magic = lambda name: name.startswith('__') and name.endswith('__')
@@ -403,9 +408,11 @@ def __lt__(self, other):
403408
return (self.filename, self.line) < (other.filename, other.line)
404409

405410

406-
def parse_options():
411+
def get_option_parser():
407412
parser = OptionParser(version=__version__,
408413
usage='Usage: pep257 [options] [<file|dir>...]')
414+
parser.config_options = ('explain', 'source', 'ignore', 'match',
415+
'match-dir', 'debug', 'verbose')
409416
option = parser.add_option
410417
option('-e', '--explain', action='store_true',
411418
help='show explanation of each error')
@@ -425,7 +432,9 @@ def parse_options():
425432
"all dirs that don't start with a dot")
426433
option('-d', '--debug', action='store_true',
427434
help='print debug information')
428-
return parser.parse_args()
435+
option('-v', '--verbose', action='store_true',
436+
help='print status information')
437+
return parser
429438

430439

431440
def collect(names, match=lambda name: True, match_dir=lambda name: True):
@@ -463,6 +472,7 @@ def check(filenames, ignore=()):
463472
464473
"""
465474
for filename in filenames:
475+
log.info('Checking file %s.', filename)
466476
try:
467477
with open(filename) as file:
468478
source = file.read()
@@ -476,15 +486,84 @@ def check(filenames, ignore=()):
476486
yield SyntaxError('invalid syntax in file %s' % filename)
477487

478488

479-
def main(options, arguments):
489+
def get_options(args, opt_parser):
490+
config = RawConfigParser()
491+
parent = tail = args and os.path.abspath(os.path.commonprefix(args))
492+
while tail:
493+
for fn in PROJECT_CONFIG:
494+
full_path = os.path.join(parent, fn)
495+
if config.read(full_path):
496+
log.info('local configuration: in %s.', full_path)
497+
break
498+
parent, tail = os.path.split(parent)
499+
500+
new_options = None
501+
if config.has_section('pep257'):
502+
option_list = dict([(o.dest, o.type or o.action)
503+
for o in opt_parser.option_list])
504+
505+
# First, read the default values
506+
new_options, _ = opt_parser.parse_args([])
507+
508+
# Second, parse the configuration
509+
pep257_section = 'pep257'
510+
for opt in config.options(pep257_section):
511+
if opt.replace('_', '-') not in opt_parser.config_options:
512+
print("Unknown option '{}' ignored".format(opt))
513+
continue
514+
normalized_opt = opt.replace('-', '_')
515+
opt_type = option_list[normalized_opt]
516+
if opt_type in ('int', 'count'):
517+
value = config.getint(pep257_section, opt)
518+
elif opt_type == 'string':
519+
value = config.get(pep257_section, opt)
520+
else:
521+
assert opt_type in ('store_true', 'store_false')
522+
value = config.getboolean(pep257_section, opt)
523+
setattr(new_options, normalized_opt, value)
524+
525+
# Third, overwrite with the command-line options
526+
options, _ = opt_parser.parse_args(values=new_options)
527+
log.debug("options: %s", options)
528+
return options
529+
530+
531+
def setup_stream_handler(options):
532+
if log.handlers:
533+
for handler in log.handlers:
534+
log.removeHandler(handler)
535+
stream_handler = logging.StreamHandler(sys.stdout)
536+
stream_handler.setLevel(logging.WARNING)
480537
if options.debug:
481-
log.setLevel(logging.DEBUG)
482-
log.debug("starting pep257 in debug mode.")
483-
Error.explain = options.explain
484-
Error.source = options.source
538+
stream_handler.setLevel(logging.DEBUG)
539+
elif options.verbose:
540+
stream_handler.setLevel(logging.INFO)
541+
else:
542+
stream_handler.setLevel(logging.WARNING)
543+
log.addHandler(stream_handler)
544+
545+
546+
def main():
547+
opt_parser = get_option_parser()
548+
# setup the logger before parsing the config file, so that command line
549+
# arguments for debug / verbose will be printed.
550+
options, arguments = opt_parser.parse_args()
551+
setup_stream_handler(options)
552+
# We parse the files before opening the config file, since it changes where
553+
# we look for the file.
554+
options = get_options(arguments, opt_parser)
555+
# Setup the handler again with values from the config file.
556+
setup_stream_handler(options)
557+
485558
collected = collect(arguments or ['.'],
486559
match=re(options.match + '$').match,
487560
match_dir=re(options.match_dir + '$').match)
561+
562+
log.debug("starting pep257 in debug mode.")
563+
564+
Error.explain = options.explain
565+
Error.source = options.source
566+
collected = list(collected)
488567
code = 0
489568
for error in check(collected, ignore=options.ignore.split(',')):
490569
sys.stderr.write('%s\n' % error)
@@ -799,6 +878,6 @@ def SKIP_check_return_type(self, function, docstring):
799878

800879
if __name__ == '__main__':
801880
try:
802-
sys.exit(main(*parse_options()))
881+
sys.exit(main())
803882
except KeyboardInterrupt:
804883
pass

test_pep257.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
# -*- coding: utf-8 -*-
33
from __future__ import with_statement
44

5-
import pep257
5+
import os
66
import mock
7+
import shutil
8+
import shlex
9+
import tempfile
10+
import textwrap
11+
import subprocess
712

13+
import pep257
814

915
__all__ = ()
1016

@@ -31,3 +37,65 @@ def test_ignore_list():
3137
errors = tuple(pep257.check(['filepath'], ignore=['D100', 'D202']))
3238
error_codes = set(error.code for error in errors)
3339
assert error_codes == expected_error_codes - set(('D100', 'D202'))
40+
41+
42+
def test_config_file():
43+
"""Test that options are correctly loaded from a config file.
44+
45+
This test create a temporary directory and creates two files in it: a
46+
Python file that has two pep257 violations (D100 and D103) and a config
47+
file (tox.ini). This test alternates settings in the config file and checks
48+
that pep257 gives the correct output.
49+
50+
"""
51+
tempdir = tempfile.mkdtemp()
52+
53+
def write_config(ignore, verbose):
54+
with open(os.path.join(tempdir, 'tox.ini'), 'wt') as conf:
55+
conf.write(textwrap.dedent("""\
56+
[pep257]
57+
ignore = {0}
58+
verbose = {1}
59+
""".format(ignore, verbose)))
60+
61+
def invoke_pep257():
62+
pep257_location = os.path.join(os.path.dirname(__file__), 'pep257.py')
63+
cmd = shlex.split("python {0} {1}".format(pep257_location, tempdir))
64+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
65+
stderr=subprocess.PIPE)
66+
out, err = p.communicate()
67+
return out.decode('utf-8'), err.decode('utf-8')
68+
69+
try:
70+
with open(os.path.join(tempdir, 'example.py'), 'wt') as example:
71+
example.write(textwrap.dedent("""\
72+
def foo():
73+
pass
74+
"""))
75+
76+
write_config(ignore='D100', verbose=True)
77+
out, err = invoke_pep257()
78+
assert 'D100' not in err
79+
assert 'D103' in err
80+
assert 'example.py' in out
81+
82+
write_config(ignore='', verbose=True)
83+
out, err = invoke_pep257()
84+
assert 'D100' in err
85+
assert 'D103' in err
86+
assert 'example.py' in out
87+
88+
write_config(ignore='D100,D103', verbose=False)
89+
out, err = invoke_pep257()
90+
assert 'D100' not in err
91+
assert 'D103' not in err
92+
assert 'example.py' not in out
93+
94+
write_config(ignore='', verbose=False)
95+
out, err = invoke_pep257()
96+
assert 'D100' in err
97+
assert 'D103' in err
98+
assert 'example.py' not in out
99+
100+
finally:
101+
shutil.rmtree(tempdir)

tox.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ deps = pytest
1515
[pytest]
1616
pep8ignore =
1717
test.py E701
18+
19+
[pep257]
20+
verbose = true

0 commit comments

Comments
 (0)