"Fossies" - the Fresh Open Source Software Archive

Member "codespell-1.17.1/codespell_lib/_codespell.py" (22 May 2020, 27730 Bytes) of package /linux/misc/codespell-1.17.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "_codespell.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.16.0_vs_1.17.1.

    1 # -*- coding: utf-8 -*-
    2 #
    3 # This program is free software; you can redistribute it and/or modify
    4 # it under the terms of the GNU General Public License as published by
    5 # the Free Software Foundation; version 2 of the License.
    6 #
    7 # This program is distributed in the hope that it will be useful,
    8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
    9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   10 # GNU General Public License for more details.
   11 #
   12 # You should have received a copy of the GNU General Public License
   13 # along with this program; if not, see
   14 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
   15 """
   16 Copyright (C) 2010-2011  Lucas De Marchi <lucas.de.marchi@gmail.com>
   17 Copyright (C) 2011  ProFUSION embedded systems
   18 """
   19 
   20 from __future__ import print_function
   21 
   22 import argparse
   23 import codecs
   24 import fnmatch
   25 import os
   26 import re
   27 import sys
   28 
   29 word_regex_def = u"[\\w\\-'’`]+"
   30 encodings = ('utf-8', 'iso-8859-1')
   31 USAGE = """
   32 \t%prog [OPTIONS] [file1 file2 ... fileN]
   33 """
   34 VERSION = '1.17.1'
   35 
   36 # Users might want to link this file into /usr/local/bin, so we resolve the
   37 # symbolic link path to the real path if necessary.
   38 _data_root = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data')
   39 _builtin_dictionaries = (
   40     # name, desc, name, err in aspell, correction in aspell
   41     # The aspell tests here aren't the ideal state, but the None's are
   42     # realistic for obscure words
   43     ('clear', 'for unambiguous errors', '', False, None),
   44     ('rare', 'for rare but valid words', '_rare', None, None),
   45     ('informal', 'for informal words', '_informal', True, True),
   46     ('code', 'for words common to code and/or mathematics', '_code', None, None),  # noqa: E501
   47     ('names', 'for valid proper names that might be typos', '_names', None, None),  # noqa: E501
   48     ('en-GB_to_en-US', 'for corrections from en-GB to en-US', '_en-GB_to_en-US', True, True),  # noqa: E501
   49 )
   50 _builtin_default = 'clear,rare'
   51 
   52 # OPTIONS:
   53 #
   54 # ARGUMENTS:
   55 #    dict_filename       The file containing the dictionary of misspellings.
   56 #                        If set to '-', it will be read from stdin
   57 #    file1 .. fileN      Files to check spelling
   58 
   59 
   60 class QuietLevels(object):
   61     NONE = 0
   62     ENCODING = 1
   63     BINARY_FILE = 2
   64     DISABLED_FIXES = 4
   65     NON_AUTOMATIC_FIXES = 8
   66     FIXES = 16
   67 
   68 
   69 class GlobMatch(object):
   70     def __init__(self, pattern):
   71         if pattern:
   72             # Pattern might be a list of comma-delimited strings
   73             self.pattern_list = ','.join(pattern).split(',')
   74         else:
   75             self.pattern_list = None
   76 
   77     def match(self, filename):
   78         if self.pattern_list is None:
   79             return False
   80 
   81         for p in self.pattern_list:
   82             if fnmatch.fnmatch(filename, p):
   83                 return True
   84 
   85         return False
   86 
   87 
   88 class Misspelling(object):
   89     def __init__(self, data, fix, reason):
   90         self.data = data
   91         self.fix = fix
   92         self.reason = reason
   93 
   94 
   95 class TermColors(object):
   96     def __init__(self):
   97         self.FILE = '\033[33m'
   98         self.WWORD = '\033[31m'
   99         self.FWORD = '\033[32m'
  100         self.DISABLE = '\033[0m'
  101 
  102     def disable(self):
  103         self.FILE = ''
  104         self.WWORD = ''
  105         self.FWORD = ''
  106         self.DISABLE = ''
  107 
  108 
  109 class Summary(object):
  110     def __init__(self):
  111         self.summary = {}
  112 
  113     def update(self, wrongword):
  114         if wrongword in self.summary:
  115             self.summary[wrongword] += 1
  116         else:
  117             self.summary[wrongword] = 1
  118 
  119     def __str__(self):
  120         keys = list(self.summary.keys())
  121         keys.sort()
  122 
  123         return "\n".join(["{0}{1:{width}}".format(
  124                          key,
  125                          self.summary.get(key),
  126                          width=15 - len(key)) for key in keys])
  127 
  128 
  129 class FileOpener(object):
  130     def __init__(self, use_chardet, quiet_level):
  131         self.use_chardet = use_chardet
  132         if use_chardet:
  133             self.init_chardet()
  134         self.quiet_level = quiet_level
  135 
  136     def init_chardet(self):
  137         try:
  138             from chardet.universaldetector import UniversalDetector
  139         except ImportError:
  140             raise ImportError("There's no chardet installed to import from. "
  141                               "Please, install it and check your PYTHONPATH "
  142                               "environment variable")
  143 
  144         self.encdetector = UniversalDetector()
  145 
  146     def open(self, filename):
  147         if self.use_chardet:
  148             return self.open_with_chardet(filename)
  149         else:
  150             return self.open_with_internal(filename)
  151 
  152     def open_with_chardet(self, filename):
  153         self.encdetector.reset()
  154         with codecs.open(filename, 'rb') as f:
  155             for line in f:
  156                 self.encdetector.feed(line)
  157                 if self.encdetector.done:
  158                     break
  159         self.encdetector.close()
  160         encoding = self.encdetector.result['encoding']
  161 
  162         try:
  163             f = codecs.open(filename, 'r', encoding=encoding)
  164         except UnicodeDecodeError:
  165             print('ERROR: Could not detect encoding: %s' % filename,
  166                   file=sys.stderr)
  167             raise
  168         except LookupError:
  169             print('ERROR: %s -- Don\'t know how to handle encoding %s'
  170                   % (filename, encoding), file=sys.stderr)
  171             raise
  172         else:
  173             lines = f.readlines()
  174             f.close()
  175 
  176         return lines, encoding
  177 
  178     def open_with_internal(self, filename):
  179         curr = 0
  180         while True:
  181             try:
  182                 f = codecs.open(filename, 'r', encoding=encodings[curr])
  183             except UnicodeDecodeError:
  184                 if not self.quiet_level & QuietLevels.ENCODING:
  185                     print('WARNING: Decoding file %s' % filename,
  186                           file=sys.stderr)
  187                     print('WARNING: using encoding=%s failed. '
  188                           % encodings[curr], file=sys.stderr)
  189                     try:
  190                         print('WARNING: Trying next encoding: %s'
  191                               % encodings[curr + 1], file=sys.stderr)
  192                     except IndexError:
  193                         pass
  194 
  195                 curr += 1
  196             else:
  197                 lines = f.readlines()
  198                 f.close()
  199                 break
  200         if not lines:
  201             raise Exception('Unknown encoding')
  202 
  203         encoding = encodings[curr]
  204 
  205         return lines, encoding
  206 
  207 # -.-:-.-:-.-:-.:-.-:-.-:-.-:-.-:-.:-.-:-.-:-.-:-.-:-.:-.-:-
  208 
  209 
  210 def parse_options(args):
  211     parser = argparse.ArgumentParser()
  212 
  213     parser.set_defaults(colors=sys.stdout.isatty())
  214     parser.add_argument('--version', action='version', version=VERSION)
  215 
  216     parser.add_argument('-d', '--disable-colors',
  217                         action='store_false', dest='colors',
  218                         help='disable colors, even when printing to terminal '
  219                              '(always set for Windows)')
  220     parser.add_argument('-c', '--enable-colors',
  221                         action='store_true', dest='colors',
  222                         help='enable colors, even when not printing to '
  223                              'terminal')
  224 
  225     parser.add_argument('-w', '--write-changes',
  226                         action='store_true', default=False,
  227                         help='write changes in place if possible')
  228 
  229     parser.add_argument('-D', '--dictionary',
  230                         action='append',
  231                         help='Custom dictionary file that contains spelling '
  232                              'corrections. If this flag is not specified or '
  233                              'equals "-" then the default dictionary is used. '
  234                              'This option can be specified multiple times.')
  235     builtin_opts = ', '.join(
  236         '%r %s' % (d[0], d[1]) for d in _builtin_dictionaries)
  237     parser.add_argument('--builtin',
  238                         dest='builtin', default=_builtin_default,
  239                         metavar='BUILTIN-LIST',
  240                         help='Comma-separated list of builtin dictionaries '
  241                         'to include (when "-D -" or no "-D" is passed). '
  242                         'Current options are:\n%s. The default is '
  243                         '"--builtin %s".'
  244                         % (builtin_opts, _builtin_default))
  245     parser.add_argument('-I', '--ignore-words',
  246                         action='append', metavar='FILE',
  247                         help='File that contains words which will be ignored '
  248                              'by codespell. File must contain 1 word per line.'
  249                              ' Words are case sensitive based on how they are '
  250                              'written in the dictionary file')
  251     parser.add_argument('-L', '--ignore-words-list',
  252                         action='append', metavar='WORDS',
  253                         help='Comma separated list of words to be ignored '
  254                              'by codespell. Words are case sensitive based on '
  255                              'how they are written in the dictionary file')
  256     parser.add_argument('-r', '--regex',
  257                         action='store', type=str,
  258                         help='Regular expression which is used to find words. '
  259                              'By default any alphanumeric character, the '
  260                              'underscore, the hyphen, and the apostrophe is '
  261                              'used to build words. This option cannot be '
  262                              'specified together with --write-changes.')
  263     parser.add_argument('-s', '--summary',
  264                         action='store_true', default=False,
  265                         help='print summary of fixes')
  266 
  267     parser.add_argument('-S', '--skip',
  268                         action='append',
  269                         help='Comma-separated list of files to skip. It '
  270                              'accepts globs as well. E.g.: if you want '
  271                              'codespell to skip .eps and .txt files, '
  272                              'you\'d give "*.eps,*.txt" to this option.')
  273 
  274     parser.add_argument('-x', '--exclude-file', type=str, metavar='FILE',
  275                         help='FILE with lines that should not be changed')
  276 
  277     parser.add_argument('-i', '--interactive',
  278                         action='store', type=int, default=0,
  279                         help='Set interactive mode when writing changes. '
  280                              '0: no interactivity. 1: ask for confirmation. '
  281                              '2 ask user to choose one fix when more than one '
  282                              'is available. 3: both 1 and 2')
  283 
  284     parser.add_argument('-q', '--quiet-level',
  285                         action='store', type=int, default=0,
  286                         help='Bitmask that allows codespell to run quietly. '
  287                              '0: the default, in which all messages are '
  288                              'printed. 1: disable warnings about wrong '
  289                              'encoding. 2: disable warnings about binary '
  290                              'file. 4: shut down warnings about automatic '
  291                              'fixes that were disabled in dictionary. '
  292                              '8: don\'t print anything for non-automatic '
  293                              'fixes. 16: don\'t print fixed files.')
  294 
  295     parser.add_argument('-e', '--hard-encoding-detection',
  296                         action='store_true', default=False,
  297                         help='Use chardet to detect the encoding of each '
  298                              'file. This can slow down codespell, but is more '
  299                              'reliable in detecting encodings other than '
  300                              'utf-8, iso8859-1, and ascii.')
  301 
  302     parser.add_argument('-f', '--check-filenames',
  303                         action='store_true', default=False,
  304                         help='check file names as well')
  305 
  306     parser.add_argument('-H', '--check-hidden',
  307                         action='store_true', default=False,
  308                         help='check hidden files (those starting with ".") as '
  309                              'well')
  310     parser.add_argument('-A', '--after-context', type=int, metavar='LINES',
  311                         help='print LINES of trailing context')
  312     parser.add_argument('-B', '--before-context', type=int, metavar='LINES',
  313                         help='print LINES of leading context')
  314     parser.add_argument('-C', '--context', type=int, metavar='LINES',
  315                         help='print LINES of surrounding context')
  316 
  317     parser.add_argument('files', nargs='*',
  318                         help='files or directories to check')
  319 
  320     options = parser.parse_args(list(args))
  321 
  322     if not options.files:
  323         options.files.append('.')
  324 
  325     return options, parser
  326 
  327 
  328 def build_exclude_hashes(filename, exclude_lines):
  329     with codecs.open(filename, 'r') as f:
  330         for line in f:
  331             exclude_lines.add(line)
  332 
  333 
  334 def build_ignore_words(filename, ignore_words):
  335     with codecs.open(filename, mode='r', encoding='utf-8') as f:
  336         for line in f:
  337             ignore_words.add(line.strip())
  338 
  339 
  340 def build_dict(filename, misspellings, ignore_words):
  341     with codecs.open(filename, mode='r', encoding='utf-8') as f:
  342         for line in f:
  343             [key, data] = line.split('->')
  344             # TODO for now, convert both to lower. Someday we can maybe add
  345             # support for fixing caps.
  346             key = key.lower()
  347             data = data.lower()
  348             if key in ignore_words:
  349                 continue
  350             data = data.strip()
  351             fix = data.rfind(',')
  352 
  353             if fix < 0:
  354                 fix = True
  355                 reason = ''
  356             elif fix == (len(data) - 1):
  357                 data = data[:fix]
  358                 reason = ''
  359                 fix = False
  360             else:
  361                 reason = data[fix + 1:].strip()
  362                 data = data[:fix]
  363                 fix = False
  364 
  365             misspellings[key] = Misspelling(data, fix, reason)
  366 
  367 
  368 def is_hidden(filename, check_hidden):
  369     bfilename = os.path.basename(filename)
  370 
  371     return bfilename not in ('', '.', '..') and \
  372         (not check_hidden and bfilename[0] == '.')
  373 
  374 
  375 def is_text_file(filename):
  376     with open(filename, mode='rb') as f:
  377         s = f.read(1024)
  378     if b'\x00' in s:
  379         return False
  380     return True
  381 
  382 
  383 def fix_case(word, fixword):
  384     if word == word.capitalize():
  385         return fixword.capitalize()
  386     elif word == word.upper():
  387         return fixword.upper()
  388     # they are both lower case
  389     # or we don't have any idea
  390     return fixword
  391 
  392 
  393 def ask_for_word_fix(line, wrongword, misspelling, interactivity):
  394     if interactivity <= 0:
  395         return misspelling.fix, fix_case(wrongword, misspelling.data)
  396 
  397     if misspelling.fix and interactivity & 1:
  398         r = ''
  399         fixword = fix_case(wrongword, misspelling.data)
  400         while not r:
  401             print("%s\t%s ==> %s (Y/n) " % (line, wrongword, fixword), end='')
  402             r = sys.stdin.readline().strip().upper()
  403             if not r:
  404                 r = 'Y'
  405             if r != 'Y' and r != 'N':
  406                 print("Say 'y' or 'n'")
  407                 r = ''
  408 
  409         if r == 'N':
  410             misspelling.fix = False
  411             misspelling.fixword = ''
  412 
  413     elif (interactivity & 2) and not misspelling.reason:
  414         # if it is not disabled, i.e. it just has more than one possible fix,
  415         # we ask the user which word to use
  416 
  417         r = ''
  418         opt = list(map(lambda x: x.strip(), misspelling.data.split(',')))
  419         while not r:
  420             print("%s Choose an option (blank for none): " % line, end='')
  421             for i in range(len(opt)):
  422                 fixword = fix_case(wrongword, opt[i])
  423                 print(" %d) %s" % (i, fixword), end='')
  424             print(": ", end='')
  425             sys.stdout.flush()
  426 
  427             n = sys.stdin.readline().strip()
  428             if not n:
  429                 break
  430 
  431             try:
  432                 n = int(n)
  433                 r = opt[n]
  434             except (ValueError, IndexError):
  435                 print("Not a valid option\n")
  436 
  437         if r:
  438             misspelling.fix = True
  439             misspelling.data = r
  440 
  441     return misspelling.fix, fix_case(wrongword, misspelling.data)
  442 
  443 
  444 def print_context(lines, index, context):
  445     # context = (context_before, context_after)
  446     for i in range(index - context[0], index + context[1] + 1):
  447         if 0 <= i < len(lines):
  448             print('%s %s' % ('>' if i == index else ':', lines[i].rstrip()))
  449 
  450 
  451 def parse_file(filename, colors, summary, misspellings, exclude_lines,
  452                file_opener, word_regex, context, options):
  453     bad_count = 0
  454     lines = None
  455     changed = False
  456     encoding = encodings[0]  # if not defined, use UTF-8
  457 
  458     if filename == '-':
  459         f = sys.stdin
  460         lines = f.readlines()
  461     else:
  462         # ignore binary files
  463         if not os.path.isfile(filename):
  464             return 0
  465         if options.check_filenames:
  466             for word in word_regex.findall(filename):
  467                 lword = word.lower()
  468                 if lword not in misspellings:
  469                     continue
  470                 fix = misspellings[lword].fix
  471                 fixword = fix_case(word, misspellings[lword].data)
  472 
  473                 if summary and fix:
  474                     summary.update(lword)
  475 
  476                 cfilename = "%s%s%s" % (colors.FILE, filename, colors.DISABLE)
  477                 cwrongword = "%s%s%s" % (colors.WWORD, word, colors.DISABLE)
  478                 crightword = "%s%s%s" % (colors.FWORD, fixword, colors.DISABLE)
  479 
  480                 if misspellings[lword].reason:
  481                     if options.quiet_level & QuietLevels.DISABLED_FIXES:
  482                         continue
  483                     creason = "  | %s%s%s" % (colors.FILE,
  484                                               misspellings[lword].reason,
  485                                               colors.DISABLE)
  486                 else:
  487                     if options.quiet_level & QuietLevels.NON_AUTOMATIC_FIXES:
  488                         continue
  489                     creason = ''
  490 
  491                 bad_count += 1
  492 
  493                 print("%(FILENAME)s: %(WRONGWORD)s"
  494                       " ==> %(RIGHTWORD)s%(REASON)s"
  495                       % {'FILENAME': cfilename,
  496                          'WRONGWORD': cwrongword,
  497                          'RIGHTWORD': crightword, 'REASON': creason})
  498 
  499         text = is_text_file(filename)
  500         if not text:
  501             if not options.quiet_level & QuietLevels.BINARY_FILE:
  502                 print("WARNING: Binary file: %s " % filename, file=sys.stderr)
  503             return 0
  504         try:
  505             lines, encoding = file_opener.open(filename)
  506         except Exception:
  507             return 0
  508 
  509     for i, line in enumerate(lines):
  510         if line in exclude_lines:
  511             continue
  512 
  513         fixed_words = set()
  514         asked_for = set()
  515 
  516         for word in word_regex.findall(line):
  517             lword = word.lower()
  518             if lword in misspellings:
  519                 context_shown = False
  520                 fix = misspellings[lword].fix
  521                 fixword = fix_case(word, misspellings[lword].data)
  522 
  523                 if options.interactive and lword not in asked_for:
  524                     if context is not None:
  525                         context_shown = True
  526                         print_context(lines, i, context)
  527                     fix, fixword = ask_for_word_fix(
  528                         lines[i], word, misspellings[lword],
  529                         options.interactive)
  530                     asked_for.add(lword)
  531 
  532                 if summary and fix:
  533                     summary.update(lword)
  534 
  535                 if word in fixed_words:  # can skip because of re.sub below
  536                     continue
  537 
  538                 if options.write_changes and fix:
  539                     changed = True
  540                     lines[i] = re.sub(r'\b%s\b' % word, fixword, lines[i])
  541                     fixed_words.add(word)
  542                     continue
  543 
  544                 # otherwise warning was explicitly set by interactive mode
  545                 if (options.interactive & 2 and not fix and not
  546                         misspellings[lword].reason):
  547                     continue
  548 
  549                 cfilename = "%s%s%s" % (colors.FILE, filename, colors.DISABLE)
  550                 cline = "%s%d%s" % (colors.FILE, i + 1, colors.DISABLE)
  551                 cwrongword = "%s%s%s" % (colors.WWORD, word, colors.DISABLE)
  552                 crightword = "%s%s%s" % (colors.FWORD, fixword, colors.DISABLE)
  553 
  554                 if misspellings[lword].reason:
  555                     if options.quiet_level & QuietLevels.DISABLED_FIXES:
  556                         continue
  557 
  558                     creason = "  | %s%s%s" % (colors.FILE,
  559                                               misspellings[lword].reason,
  560                                               colors.DISABLE)
  561                 else:
  562                     if options.quiet_level & QuietLevels.NON_AUTOMATIC_FIXES:
  563                         continue
  564 
  565                     creason = ''
  566 
  567                 # If we get to this point (uncorrected error) we should change
  568                 # our bad_count and thus return value
  569                 bad_count += 1
  570 
  571                 if (not context_shown) and (context is not None):
  572                     print_context(lines, i, context)
  573                 if filename != '-':
  574                     print("%(FILENAME)s:%(LINE)s: %(WRONGWORD)s "
  575                           "==> %(RIGHTWORD)s%(REASON)s"
  576                           % {'FILENAME': cfilename, 'LINE': cline,
  577                              'WRONGWORD': cwrongword,
  578                              'RIGHTWORD': crightword, 'REASON': creason})
  579                 else:
  580                     print("%(LINE)s: %(STRLINE)s\n\t%(WRONGWORD)s "
  581                           "==> %(RIGHTWORD)s%(REASON)s"
  582                           % {'LINE': cline, 'STRLINE': line.strip(),
  583                              'WRONGWORD': cwrongword,
  584                              'RIGHTWORD': crightword, 'REASON': creason})
  585 
  586     if changed:
  587         if filename == '-':
  588             print("---")
  589             for line in lines:
  590                 print(line, end='')
  591         else:
  592             if not options.quiet_level & QuietLevels.FIXES:
  593                 print("%sFIXED:%s %s"
  594                       % (colors.FWORD, colors.DISABLE, filename),
  595                       file=sys.stderr)
  596             with codecs.open(filename, 'w', encoding=encoding) as f:
  597                 f.writelines(lines)
  598     return bad_count
  599 
  600 
  601 def _script_main():
  602     """Wrap to main() for setuptools."""
  603     return main(*sys.argv[1:])
  604 
  605 
  606 def main(*args):
  607     """Contains flow control"""
  608     options, parser = parse_options(args)
  609 
  610     if options.regex and options.write_changes:
  611         print('ERROR: --write-changes cannot be used together with '
  612               '--regex')
  613         parser.print_help()
  614         return 1
  615     word_regex = options.regex or word_regex_def
  616     try:
  617         word_regex = re.compile(word_regex)
  618     except re.error as err:
  619         print('ERROR: invalid regular expression "%s" (%s)' %
  620               (word_regex, err), file=sys.stderr)
  621         parser.print_help()
  622         return 1
  623 
  624     ignore_words_files = options.ignore_words or []
  625     ignore_words = set()
  626     for ignore_words_file in ignore_words_files:
  627         if not os.path.isfile(ignore_words_file):
  628             print('ERROR: cannot find ignore-words file: %s' %
  629                   ignore_words_file, file=sys.stderr)
  630             parser.print_help()
  631             return 1
  632         build_ignore_words(ignore_words_file, ignore_words)
  633 
  634     ignore_words_list = options.ignore_words_list or []
  635     for comma_separated_words in ignore_words_list:
  636         for word in comma_separated_words.split(','):
  637             ignore_words.add(word.strip())
  638 
  639     if options.dictionary:
  640         dictionaries = options.dictionary
  641     else:
  642         dictionaries = ['-']
  643     use_dictionaries = list()
  644     for dictionary in dictionaries:
  645         if dictionary == "-":
  646             # figure out which builtin dictionaries to use
  647             use = sorted(set(options.builtin.split(',')))
  648             for u in use:
  649                 for builtin in _builtin_dictionaries:
  650                     if builtin[0] == u:
  651                         use_dictionaries.append(
  652                             os.path.join(_data_root, 'dictionary%s.txt'
  653                                          % (builtin[2],)))
  654                         break
  655                 else:
  656                     print('ERROR: Unknown builtin dictionary: %s' % (u,),
  657                           file=sys.stderr)
  658                     parser.print_help()
  659                     return 1
  660         else:
  661             if not os.path.isfile(dictionary):
  662                 print('ERROR: cannot find dictionary file: %s' % dictionary,
  663                       file=sys.stderr)
  664                 parser.print_help()
  665                 return 1
  666             use_dictionaries.append(dictionary)
  667     misspellings = dict()
  668     for dictionary in use_dictionaries:
  669         build_dict(dictionary, misspellings, ignore_words)
  670     colors = TermColors()
  671     if not options.colors or sys.platform == 'win32':
  672         colors.disable()
  673 
  674     if options.summary:
  675         summary = Summary()
  676     else:
  677         summary = None
  678 
  679     context = None
  680     if options.context is not None:
  681         if (options.before_context is not None) or \
  682                 (options.after_context is not None):
  683             print('ERROR: --context/-C cannot be used together with '
  684                   '--context-before/-B or --context-after/-A')
  685             parser.print_help()
  686             return 1
  687         context_both = max(0, options.context)
  688         context = (context_both, context_both)
  689     elif (options.before_context is not None) or \
  690             (options.after_context is not None):
  691         context_before = 0
  692         context_after = 0
  693         if options.before_context is not None:
  694             context_before = max(0, options.before_context)
  695         if options.after_context is not None:
  696             context_after = max(0, options.after_context)
  697         context = (context_before, context_after)
  698 
  699     exclude_lines = set()
  700     if options.exclude_file:
  701         build_exclude_hashes(options.exclude_file, exclude_lines)
  702 
  703     file_opener = FileOpener(options.hard_encoding_detection,
  704                              options.quiet_level)
  705     glob_match = GlobMatch(options.skip)
  706 
  707     bad_count = 0
  708     for filename in options.files:
  709         # ignore hidden files
  710         if is_hidden(filename, options.check_hidden):
  711             continue
  712 
  713         if os.path.isdir(filename):
  714             for root, dirs, files in os.walk(filename):
  715                 if glob_match.match(root):  # skip (absolute) directories
  716                     del dirs[:]
  717                     continue
  718                 for file_ in files:
  719                     if glob_match.match(file_):  # skip files
  720                         continue
  721                     fname = os.path.join(root, file_)
  722                     if glob_match.match(fname):  # skip paths
  723                         continue
  724                     if not os.path.isfile(fname) or not os.path.getsize(fname):
  725                         continue
  726                     bad_count += parse_file(
  727                         fname, colors, summary, misspellings, exclude_lines,
  728                         file_opener, word_regex, context, options)
  729 
  730                 # skip (relative) directories
  731                 dirs[:] = [dir_ for dir_ in dirs if not glob_match.match(dir_)]
  732 
  733         else:
  734             bad_count += parse_file(
  735                 filename, colors, summary, misspellings, exclude_lines,
  736                 file_opener, word_regex, context, options)
  737 
  738     if summary:
  739         print("\n-------8<-------\nSUMMARY:")
  740         print(summary)
  741     return bad_count