20
20
from itertools import takewhile , dropwhile , chain
21
21
from optparse import OptionParser
22
22
from re import compile as re
23
+ import itertools
24
+
23
25
try : # Python 3.x
24
26
from ConfigParser import RawConfigParser
25
27
except ImportError : # Python 2.x
@@ -61,6 +63,9 @@ def next(obj, default=nothing):
61
63
__all__ = ('check' , 'collect' )
62
64
63
65
PROJECT_CONFIG = ('setup.cfg' , 'tox.ini' , '.pep257' )
66
+ NO_VIOLATIONS_RETURN_CODE = 0
67
+ VIOLATIONS_RETURN_CODE = 1
68
+ INVALID_OPTIONS_RETURN_CODE = 2
64
69
65
70
66
71
def humanize (string ):
@@ -549,7 +554,7 @@ def create_group(cls, prefix, name):
549
554
def get_error_codes (cls ):
550
555
for group in cls .groups :
551
556
for error in group .errors :
552
- yield error
557
+ yield error . code
553
558
554
559
@classmethod
555
560
def to_rst (cls ):
@@ -612,19 +617,38 @@ def to_rst(cls):
612
617
'"signature"' )
613
618
614
619
620
+ class Conventions (object ):
621
+ pep257 = set (ErrorRegistry .get_error_codes ())
622
+
623
+
615
624
def get_option_parser ():
616
625
parser = OptionParser (version = __version__ ,
617
626
usage = 'Usage: pep257 [options] [<file|dir>...]' )
618
- parser .config_options = ('explain' , 'source' , 'ignore' , 'match' ,
619
- 'match-dir' , 'debug' , 'verbose' , 'count' )
627
+ parser .config_options = ('explain' , 'source' , 'ignore' , 'match' , 'select' ,
628
+ 'match-dir' , 'debug' , 'verbose' , 'count' ,
629
+ 'convention' )
620
630
option = parser .add_option
621
631
option ('-e' , '--explain' , action = 'store_true' ,
622
632
help = 'show explanation of each error' )
623
633
option ('-s' , '--source' , action = 'store_true' ,
624
634
help = 'show source for each error' )
635
+ option ('--select' , metavar = '<codes>' , default = '' ,
636
+ help = 'choose the basic list of checked errors by specifying which '
637
+ 'errors to check for (with a list of comma-separated error '
638
+ 'codes). for example: --select=D101,D202' )
625
639
option ('--ignore' , metavar = '<codes>' , default = '' ,
626
- help = 'ignore a list comma-separated error codes, '
627
- 'for example: --ignore=D101,D202' )
640
+ help = 'choose the basic list of checked errors by specifying which '
641
+ 'errors to ignore (with a list of comma-separated error '
642
+ 'codes). for example: --ignore=D101,D202' )
643
+ option ('--convention' , metavar = '<name>' , default = '' ,
644
+ help = 'choose the basic list of checked errors by specifying an '
645
+ 'existing convention. for example: --convention=pep257' )
646
+ option ('--add-select' , metavar = '<codes>' , default = '' ,
647
+ help = 'amend the list of errors to check for by specifying more '
648
+ 'error codes to check.' )
649
+ option ('--add-ignore' , metavar = '<codes>' , default = '' ,
650
+ help = 'amend the list of errors to check for by specifying more '
651
+ 'error codes to ignore.' )
628
652
option ('--match' , metavar = '<pattern>' , default = '(?!test_).*\.py' ,
629
653
help = "check only files that exactly match <pattern> regular "
630
654
"expression; default is --match='(?!test_).*\.py' which "
@@ -665,25 +689,34 @@ def collect(names, match=lambda name: True, match_dir=lambda name: True):
665
689
yield name
666
690
667
691
668
- def check (filenames , ignore = () ):
692
+ def check (filenames , select = None , ignore = None ):
669
693
"""Generate PEP 257 errors that exist in `filenames` iterable.
670
694
671
- Skips errors with error-codes defined in `ignore ` iterable.
695
+ Only returns errors with error-codes defined in `checked_codes ` iterable.
672
696
673
697
Example
674
698
-------
675
- >>> check(['pep257.py'], ignore =['D100'])
699
+ >>> check(['pep257.py'], checked_codes =['D100'])
676
700
<generator object check at 0x...>
677
701
678
702
"""
703
+ if select and ignore :
704
+ raise ValueError ('Cannot pass both select and ignore. They are '
705
+ 'mutually exclusive.' )
706
+ elif select or ignore :
707
+ checked_codes = (select or
708
+ set (ErrorRegistry .get_error_codes ()) - set (ignore ))
709
+ else :
710
+ checked_codes = Conventions .pep257
711
+
679
712
for filename in filenames :
680
713
log .info ('Checking file %s.' , filename )
681
714
try :
682
715
with tokenize_open (filename ) as file :
683
716
source = file .read ()
684
717
for error in PEP257Checker ().check_source (source , filename ):
685
718
code = getattr (error , 'code' , None )
686
- if code is not None and code not in ignore :
719
+ if code in checked_codes :
687
720
yield error
688
721
except (EnvironmentError , AllError ):
689
722
yield sys .exc_info ()[1 ]
@@ -693,7 +726,7 @@ def check(filenames, ignore=()):
693
726
694
727
def get_options (args , opt_parser ):
695
728
config = RawConfigParser ()
696
- parent = tail = args and os .path .abspath (os .path .commonprefix (args ))
729
+ parent = tail = os .path .abspath (os .path .commonprefix (args ))
697
730
while tail :
698
731
for fn in PROJECT_CONFIG :
699
732
full_path = os .path .join (parent , fn )
@@ -714,7 +747,7 @@ def get_options(args, opt_parser):
714
747
pep257_section = 'pep257'
715
748
for opt in config .options (pep257_section ):
716
749
if opt .replace ('_' , '-' ) not in opt_parser .config_options :
717
- print ("Unknown option '{}' ignored" .format (opt ))
750
+ log . warning ("Unknown option '{}' ignored" .format (opt ))
718
751
continue
719
752
normalized_opt = opt .replace ('-' , '_' )
720
753
opt_type = option_list [normalized_opt ]
@@ -733,19 +766,57 @@ def get_options(args, opt_parser):
733
766
return options
734
767
735
768
736
- def setup_stream_handler (options ):
769
+ def setup_stream_handlers (options ):
770
+ """Setup logging stream handlers according to the options."""
771
+ class StdoutFilter (logging .Filter ):
772
+ def filter (self , record ):
773
+ return record .levelno in (logging .DEBUG , logging .INFO )
774
+
737
775
if log .handlers :
738
776
for handler in log .handlers :
739
777
log .removeHandler (handler )
740
- stream_handler = logging .StreamHandler (sys .stdout )
741
- stream_handler .setLevel (logging .WARNING )
778
+
779
+ stdout_handler = logging .StreamHandler (sys .stdout )
780
+ stdout_handler .setLevel (logging .WARNING )
781
+ stdout_handler .addFilter (StdoutFilter ())
742
782
if options .debug :
743
- stream_handler .setLevel (logging .DEBUG )
783
+ stdout_handler .setLevel (logging .DEBUG )
744
784
elif options .verbose :
745
- stream_handler .setLevel (logging .INFO )
785
+ stdout_handler .setLevel (logging .INFO )
786
+ else :
787
+ stdout_handler .setLevel (logging .WARNING )
788
+ log .addHandler (stdout_handler )
789
+
790
+ stderr_handler = logging .StreamHandler (sys .stderr )
791
+ stderr_handler .setLevel (logging .WARNING )
792
+ log .addHandler (stderr_handler )
793
+
794
+
795
+ def get_checked_error_codes (options ):
796
+ codes = set (ErrorRegistry .get_error_codes ())
797
+ if options .ignore :
798
+ checked_codes = codes - set (options .ignore .split (',' ))
799
+ elif options .select :
800
+ checked_codes = set (options .select .split (',' ))
801
+ elif options .convention :
802
+ checked_codes = getattr (Conventions , options .convention )
746
803
else :
747
- stream_handler .setLevel (logging .WARNING )
748
- log .addHandler (stream_handler )
804
+ checked_codes = Conventions .pep257
805
+ checked_codes -= set (options .add_ignore .split (',' ))
806
+ checked_codes |= set (options .add_select .split (',' ))
807
+ return checked_codes - set ('' )
808
+
809
+
810
+ def validate_options (options ):
811
+ mutually_exclusive = ('ignore' , 'select' , 'convention' )
812
+ for opt1 , opt2 in itertools .permutations (mutually_exclusive , 2 ):
813
+ if getattr (options , opt1 ) and getattr (options , opt2 ):
814
+ log .error ('Cannot pass both {0} and {1}. They are '
815
+ 'mutually exclusive.' .format (opt1 , opt2 ))
816
+ return False
817
+ if options .convention and not hasattr (Conventions , options .convention ):
818
+ return False
819
+ return True
749
820
750
821
751
822
def run_pep257 ():
@@ -754,12 +825,14 @@ def run_pep257():
754
825
# setup the logger before parsing the config file, so that command line
755
826
# arguments for debug / verbose will be printed.
756
827
options , arguments = opt_parser .parse_args ()
757
- setup_stream_handler (options )
828
+ setup_stream_handlers (options )
758
829
# We parse the files before opening the config file, since it changes where
759
830
# we look for the file.
760
831
options = get_options (arguments , opt_parser )
832
+ if not validate_options (options ):
833
+ return INVALID_OPTIONS_RETURN_CODE
761
834
# Setup the handler again with values from the config file.
762
- setup_stream_handler (options )
835
+ setup_stream_handlers (options )
763
836
764
837
collected = collect (arguments or ['.' ],
765
838
match = re (options .match + '$' ).match ,
@@ -770,12 +843,13 @@ def run_pep257():
770
843
Error .explain = options .explain
771
844
Error .source = options .source
772
845
collected = list (collected )
773
- errors = check (collected , ignore = options .ignore .split (',' ))
774
- code = 0
846
+ checked_codes = get_checked_error_codes (options )
847
+ errors = check (collected , select = checked_codes )
848
+ code = NO_VIOLATIONS_RETURN_CODE
775
849
count = 0
776
850
for error in errors :
777
851
sys .stderr .write ('%s\n ' % error )
778
- code = 1
852
+ code = VIOLATIONS_RETURN_CODE
779
853
count += 1
780
854
if options .count :
781
855
print (count )
0 commit comments