diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 404f719..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: python -before_install: - - sudo apt-get install gpg -python: - - "2.6" - - "2.7" - - "3.3" -install: - - make install -# command to run tests, e.g. python setup.py test -script: - - make test diff --git a/MANIFEST.in b/MANIFEST.in index 29721a5..2f5addb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,15 @@ include LICENSE include requirements.txt -graft docs +recursive-include examples *.py + +recursive-include docs *.rst +include docs/_static +include docs/conf.py +include docs/make.bat +include docs/Makefile +prune docs/_build + +prune private + +global-exclude *.log *~ diff --git a/Makefile b/Makefile index 3fac5ae..c992d03 100644 --- a/Makefile +++ b/Makefile @@ -34,8 +34,11 @@ cleanup-tests-all: cleanup-tests rm -rf tests/tmp cleanup-build: - mkdir buildnot - rm -rf build* + -rm MANIFEST + -rm -rf build + +cleanup-dist: + -rm -rf dist # it's not strictly necessary that gnupg2, gpg-agent, pinentry, or pip be # installed, so ignore error exit statuses for those commands @@ -90,3 +93,8 @@ docs-html: docs-zipfile: docs-html cd $(DOC_HTML_DIR) && { find . -name '*' | zip -@ -v ../$(DOC_BUILD_ZIP) ;}; @echo "Built documentation in $(DOC_BUILD_DIR)/$(DOC_BUILD_ZIP)" + +upload: cleanup-build + python setup.py bdist_egg upload --sign + #python setup.py bdist_wheel upload --sign + python setup.py sdist --formats=gztar,zip upload --sign diff --git a/TODO b/TODO index 44e8f30..fd5f352 100644 --- a/TODO +++ b/TODO @@ -5,13 +5,6 @@ It would be nice to make the file descriptors for communication with the GnuPG process configurable, and not the default, hard-coded 0=stdin 1=stdout 2=stderr. -** TODO look into RDBMS/ORM for public key storage :io:db: -see http://stackoverflow.com/q/1235594 and http://elixir.ematia.de/trac/wiki - -memcached and pymemcached were the first ones I looked at, then I discovered -redis, which seemed better. At some point we should look into using elixer, -mentioned in the above SO post, so that the backend DB can be chosen freely -and we´re not restricting users to only memcached/cassandra/redis/sqlite/etc. * Key editing :editkey: ** TODO add '--edit-key' feature :editkey: diff --git a/gnupg/_meta.py b/gnupg/_meta.py index 3aafacd..53d7620 100644 --- a/gnupg/_meta.py +++ b/gnupg/_meta.py @@ -135,13 +135,17 @@ class GPGBase(object): file for secret keys. """ self.binary = _util._find_binary(binary) - self.homedir = home if home else _util._conf + self.homedir = os.path.expanduser(home) if home else _util._conf pub = _parsers._fix_unsafe(keyring) if keyring else 'pubring.gpg' sec = _parsers._fix_unsafe(secring) if secring else 'secring.gpg' self.keyring = os.path.join(self._homedir, pub) self.secring = os.path.join(self._homedir, sec) self.options = _parsers._sanitise(options) if options else None + #: The version string of our GnuPG binary + self.binary_version = '0.0.0' + self.verbose = False + if default_preference_list: self._prefs = _check_preferences(default_preference_list, 'all') else: @@ -169,13 +173,7 @@ class GPGBase(object): log.error("GPGBase.__init__(): %s" % str(ae)) raise RuntimeError(str(ae)) else: - if verbose is True: - # The caller wants logging, but we need a valid --debug-level - # for gpg. Default to "basic", and warn about the ambiguity. - # (garrettr) - verbose = "basic" - log.warning('GPG(verbose=True) is ambiguous, defaulting to "basic" logging') - self.verbose = verbose + self._set_verbose(verbose) self.use_agent = use_agent if hasattr(self, '_agent_proc') \ @@ -183,6 +181,9 @@ class GPGBase(object): if hasattr(self, '__remove_path__'): self.__remove_path__('pinentry') + # Assign our self.binary_version attribute: + self._check_sane_and_get_gpg_version() + def __remove_path__(self, prog=None, at_exit=True): """Remove the directories containing a program from the system's ``$PATH``. If ``GPGBase.binary`` is in a directory being removed, it @@ -436,6 +437,24 @@ class GPGBase(object): _generated_keys = _util.InheritableProperty(_generated_keys_getter, _generated_keys_setter) + def _check_sane_and_get_gpg_version(self): + """Check that everything runs alright, and grab the gpg binary's + version number while we're at it, storing it as :data:`binary_version`. + + :raises RuntimeError: if we cannot invoke the gpg binary. + """ + proc = self._open_subprocess(["--list-config", "--with-colons"]) + result = self._result_map['list'](self) + self._read_data(proc.stdout, result) + if proc.returncode: + raise RuntimeError("Error invoking gpg: %s" % result.data) + else: + proc.terminate() + + version_line = str(result.data).partition(':version:')[2] + self.binary_version = version_line.split('\n')[0] + log.debug("Using GnuPG version %s" % self.binary_version) + def _make_args(self, args, passphrase=False): """Make a list of command line elements for GPG. @@ -480,10 +499,15 @@ class GPGBase(object): if self.verbose: cmd.append('--debug-all') - if ((isinstance(self.verbose, str) and - self.verbose in ['basic', 'advanced', 'expert', 'guru']) - or (isinstance(self.verbose, int) and (1<=self.verbose<=9))): - cmd.append('--debug-level %s' % self.verbose) + + if (isinstance(self.verbose, str) or + (isinstance(self.verbose, int) and (self.verbose >= 1))): + # GnuPG<=1.4.18 parses the `--debug-level` command in a way + # that is incompatible with all other GnuPG versions. :'( + if self.binary_version and (self.binary_version <= '1.4.18'): + cmd.append('--debug-level=%s' % self.verbose) + else: + cmd.append('--debug-level %s' % self.verbose) return cmd @@ -592,6 +616,36 @@ class GPGBase(object): log.debug("Finishing reading from stream %r..." % stream.__repr__()) log.debug("Read %4d bytes total" % len(result.data)) + def _set_verbose(self, verbose): + """Check and set our :data:`verbose` attribute. + The debug-level must be a string or an integer. If it is one of + the allowed strings, GnuPG will translate it internally to it's + corresponding integer level: + + basic = 1-2 + advanced = 3-5 + expert = 6-8 + guru = 9+ + + If it's not one of the recognised string levels, then then + entire argument is ignored by GnuPG. :( + + To fix that stupid behaviour, if they wanted debugging but typo'd + the string level (or specified ``verbose=True``), we'll default to + 'basic' logging. + """ + string_levels = ('basic', 'advanced', 'expert', 'guru') + + if verbose is True: + # The caller wants logging, but we need a valid --debug-level + # for gpg. Default to "basic", and warn about the ambiguity. + verbose = 'basic' + + if (isinstance(verbose, str) and not (verbose in string_levels)): + verbose = 'basic' + + self.verbose = verbose + def _collect_output(self, process, result, writer=None, stdin=None): """Drain the subprocesses output streams, writing the collected output to the result. If a writer thread (writing to the subprocess) is given, diff --git a/gnupg/_parsers.py b/gnupg/_parsers.py index 2e1767e..3dd4aad 100644 --- a/gnupg/_parsers.py +++ b/gnupg/_parsers.py @@ -475,6 +475,8 @@ def _get_options_group(group=None): '--export-secret-subkeys', '--fingerprint', '--gen-revoke', + '--hidden-encrypt-to', + '--hidden-recipient', '--list-key', '--list-keys', '--list-public-keys', @@ -566,6 +568,7 @@ def _get_options_group(group=None): '--quiet', '--sign', '--symmetric', + '--throw-keyids', '--use-agent', '--verbose', '--version', @@ -1048,7 +1051,7 @@ class ImportResult(object): :rtype: bool :returns: True if we have immport some keys, False otherwise. """ - if self.counts.not_imported > 0: return False + if self.counts['not_imported'] > 0: return False if len(self.fingerprints) == 0: return False return True __bool__ = __nonzero__ @@ -1220,6 +1223,7 @@ class Verify(object): self.status = 'signature good' self.key_id, self.username = value.split(None, 1) elif key == "VALIDSIG": + self.valid = True (self.fingerprint, self.creation_date, self.sig_timestamp, @@ -1245,17 +1249,47 @@ class Verify(object): self.valid = False self.key_id = value self.status = 'no public key' + # These are useless in Verify, since they are spit out for any + # pub/subkeys on the key, not just the one doing the signing. + # if we want to check for signatures make with expired key, + # the relevant flags are REVKEYSIG and KEYREVOKED. elif key in ("KEYEXPIRED", "SIGEXPIRED"): - # these are useless in verify, since they are spit out for any - # pub/subkeys on the key, not just the one doing the signing. - # if we want to check for signatures with expired key, - # the relevant flag is EXPKEYSIG. pass + # The signature has an expiration date which has already passed + # (EXPKEYSIG), or the signature has been revoked (REVKEYSIG): elif key in ("EXPKEYSIG", "REVKEYSIG"): - # signed with expired or revoked key self.valid = False self.key_id = value.split()[0] self.status = (('%s %s') % (key[:3], key[3:])).lower() + # This is super annoying, and bad design on the part of GnuPG, in my + # opinion. + # + # This flag can get triggered if a valid signature is made, and then + # later the key (or subkey) which created the signature is + # revoked. When this happens, GnuPG will output: + # + # REVKEYSIG 075BFD18B365D34C Test Expired Key + # VALIDSIG DAB69B05F591640B7F4DCBEA075BFD18B365D34C 2014-09-26 1411700539 0 4 0 1 2 00 4BA800F77452A6C29447FF20F4AF76ACBBE22CE2 + # KEYREVOKED + # + # Meaning that we have a timestamp for when the signature was created, + # and we know that the signature is valid, but since GnuPG gives us no + # timestamp for when the key was revoked... we have no ability to + # determine if the valid signature was made *before* the signing key + # was revoked or *after*. Meaning that if you are like me and you sign + # all your software releases and git commits, and you also practice + # good opsec by doing regular key rotations, your old signatures made + # by your expired/revoked keys (even though they were created when the + # key was still good) are considered bad because GnuPG is a + # braindamaged piece of shit. + # + # Software engineering, motherfuckers, DO YOU SPEAK IT? + # + # The signing key which created the signature has since been revoked + # (KEYREVOKED), and we're going to ignore it (but add something to the + # status message): + elif key in ("KEYREVOKED"): + self.status = '\n'.join([self.status, "key revoked"]) else: raise ValueError("Unknown status message: %r" % key) diff --git a/gnupg/_util.py b/gnupg/_util.py index e1e14ab..9f0a5c1 100644 --- a/gnupg/_util.py +++ b/gnupg/_util.py @@ -70,6 +70,9 @@ _conf = os.path.join(os.path.join(_user, '.config'), 'python-gnupg') ## Logger is disabled by default log = _logger.create_logger(0) +#: Compiled regex for determining a GnuPG binary's version: +_VERSION_STRING_REGEX = re.compile('(\d)*(\.)*(\d)*(\.)*(\d)*') + def find_encodings(enc=None, system=False): """Find functions for encoding translations for a specific codec. @@ -260,6 +263,8 @@ def _find_binary(binary=None): """ found = None if binary is not None: + if os.path.isabs(binary) and os.path.isfile(binary): + return binary if not os.path.isabs(binary): try: found = _which(binary) @@ -434,8 +439,7 @@ def _match_version_string(version): :param str version: A version string in the form x.x.x """ - regex = re.compile('(\d)*(\.)*(\d)*(\.)*(\d)*') - matched = regex.match(version) + matched = _VERSION_STRING_REGEX.match(version) g = matched.groups() major, minor, micro = int(g[0]), int(g[2]), int(g[4]) return (major, minor, micro) diff --git a/gnupg/gnupg.py b/gnupg/gnupg.py index 9aa8232..42498db 100644 --- a/gnupg/gnupg.py +++ b/gnupg/gnupg.py @@ -36,13 +36,7 @@ import os import re import textwrap -try: - from io import StringIO -except ImportError: - from cStringIO import StringIO - #: see :pep:`328` http://docs.python.org/2.5/whatsnew/pep-328.html -from . import _parsers from . import _util from . import _trust from ._meta import GPGBase @@ -128,6 +122,7 @@ class GPG(GPGBase): log.info(textwrap.dedent(""" Initialised settings: binary: %s + binary version: %s homedir: %s keyring: %s secring: %s @@ -136,9 +131,15 @@ class GPG(GPGBase): options: %s verbose: %s use_agent: %s - """ % (self.binary, self.homedir, self.keyring, self.secring, - self.default_preference_list, self.keyserver, self.options, - str(self.verbose), str(self.use_agent)))) + """ % (self.binary, + self.binary_version, + self.homedir, + self.keyring, + self.secring, + self.default_preference_list, + self.keyserver, self.options, + str(self.verbose), + str(self.use_agent)))) self._batch_dir = os.path.join(self.homedir, 'batch-files') self._key_dir = os.path.join(self.homedir, 'generated-keys') @@ -147,58 +148,46 @@ class GPG(GPGBase): self.temp_keyring = None #: The secring used in the most recently created batch file self.temp_secring = None - #: The version string of our GnuPG binary - self.binary_version = str() - ## check that everything runs alright, and grab the gpg binary's - ## version number while we're at it: - proc = self._open_subprocess(["--list-config", "--with-colons"]) - result = self._result_map['list'](self) - self._read_data(proc.stdout, result) - if proc.returncode: - raise RuntimeError("Error invoking gpg: %s" % result.data) - - version_line = str(result.data).partition(':version:')[2] - self.binary_version = version_line.split('\n')[0] - log.debug("Using GnuPG version %s" % self.binary_version) - - if _util._is_gpg2: - # Make GnuPG>=2.0.0-only methods public: - self.fix_trustdb = self._fix_trustdb - self.import_ownertrust = self._import_ownertrust - self.export_ownertrust = self._export_ownertrust - - # Make sure that the trustdb exists, or else GnuPG will exit with - # a fatal error (at least it does with GnuPG>=2.0.0): - self._create_trustdb() + # Make sure that the trustdb exists, or else GnuPG will exit with a + # fatal error (at least it does with GnuPG>=2.0.0): + self.create_trustdb() @functools.wraps(_trust._create_trustdb) - def _create_trustdb(self): + def create_trustdb(self): if self.is_gpg2(): _trust._create_trustdb(self) else: log.info("Creating the trustdb is only available with GnuPG>=2.x") + # For backward compatibility with python-gnupg<=1.3.1: + _create_trustdb = create_trustdb @functools.wraps(_trust.fix_trustdb) - def _fix_trustdb(self, trustdb=None): + def fix_trustdb(self, trustdb=None): if self.is_gpg2(): _trust.fix_trustdb(self) else: log.info("Fixing the trustdb is only available with GnuPG>=2.x") + # For backward compatibility with python-gnupg<=1.3.1: + _fix_trustdb = fix_trustdb @functools.wraps(_trust.import_ownertrust) - def _import_ownertrust(self, trustdb=None): + def import_ownertrust(self, trustdb=None): if self.is_gpg2(): _trust.import_ownertrust(self) else: log.info("Importing ownertrust is only available with GnuPG>=2.x") + # For backward compatibility with python-gnupg<=1.3.1: + _import_ownertrust = import_ownertrust @functools.wraps(_trust.export_ownertrust) - def _export_ownertrust(self, trustdb=None): + def export_ownertrust(self, trustdb=None): if self.is_gpg2(): _trust.export_ownertrust(self) else: log.info("Exporting ownertrust is only available with GnuPG>=2.x") + # For backward compatibility with python-gnupg<=1.3.1: + _export_ownertrust = export_ownertrust def is_gpg1(self): """Returns true if using GnuPG <= 1.x.""" @@ -284,15 +273,13 @@ class GPG(GPGBase): signatures. If using detached signatures, the file containing the detached signature should be specified as the ``sig_file``. - :param file file: A file descriptor object. Its type will be checked - with :func:`_util._is_file`. + :param file file: A file descriptor object. :param str sig_file: A file containing the GPG signature data for ``file``. If given, ``file`` is verified via this detached - signature. + signature. Its type will be checked with :func:`_util._is_file`. """ - fn = None result = self._result_map['verify'](self) if sig_file is None: @@ -307,19 +294,15 @@ class GPG(GPGBase): return result log.debug('verify_file(): Handling detached verification') sig_fh = None - data_fh = None try: sig_fh = open(sig_file, 'rb') - data_fh = open(file, 'rb') args = ["--verify %s -" % sig_fh.name] proc = self._open_subprocess(args) - writer = _util._threaded_copy_data(data_fh, proc.stdin) + writer = _util._threaded_copy_data(file, proc.stdin) self._collect_output(proc, result, writer, stdin=proc.stdin) finally: if sig_fh and not sig_fh.closed: sig_fh.close() - if data_fh and not data_fh.closed: - data_fh.close() return result def import_keys(self, key_data): diff --git a/gnupg/test/test_gnupg.py b/gnupg/test/test_gnupg.py index 46876f5..6a352a6 100755 --- a/gnupg/test/test_gnupg.py +++ b/gnupg/test/test_gnupg.py @@ -33,10 +33,8 @@ from glob import glob from time import localtime from time import mktime -import encodings import doctest import io -import logging import os import shutil import sys @@ -192,7 +190,9 @@ class GPGTestCase(unittest.TestCase): print(fixed) test_file = os.path.join(_files, 'cypherpunk_manifesto') self.assertTrue(os.path.isfile(test_file)) - has_shell = self.gpg.verify_file(test_file, fixed) + datafd = open(test_file, 'rb') + has_shell = self.gpg.verify_file(datafd, sig_file=fixed) + datafd.close() self.assertFalse(has_shell.valid) def test_parsers_fix_unsafe_semicolon(self): @@ -564,7 +564,7 @@ class GPGTestCase(unittest.TestCase): def test_import_only(self): """Test that key import works.""" self.test_list_keys_initial_public() - self.gpg.import_keys(KEYS_TO_IMPORT) + self.assertTrue(self.gpg.import_keys(KEYS_TO_IMPORT)) public_keys = self.gpg.list_keys() self.assertTrue(is_list_with_len(public_keys, 2), "2-element list expected") @@ -664,6 +664,7 @@ class GPGTestCase(unittest.TestCase): sig = self.gpg.sign(message, default_key=key.fingerprint, passphrase='johanborst') self.assertTrue(sig, "Good passphrase should succeed") + try: file = _util._make_binary_stream(sig.data, self.gpg._encoding) verified = self.gpg.verify_file(file) @@ -696,7 +697,7 @@ class GPGTestCase(unittest.TestCase): datafd.seek(0) sigfd.seek(0) - verified = self.gpg.verify_file(datafn, sigfn) + verified = self.gpg.verify_file(datafd, sig_file=sigfn) if key.fingerprint != verified.fingerprint: log.warn("key fingerprint: %r", key.fingerprint) @@ -707,7 +708,7 @@ class GPGTestCase(unittest.TestCase): os.unlink(sigfn) def test_signature_verification_detached_binary(self): - """Test that detached signature verification in binary mode fails.""" + """Test that detached signature verification in binary mode works.""" key = self.generate_key("Adi Shamir", "rsa.com") datafn = os.path.join(_files, 'cypherpunk_manifesto') @@ -715,7 +716,6 @@ class GPGTestCase(unittest.TestCase): datafd = open(datafn, 'rb') data = datafd.read() - datafd.close() sig = self.gpg.sign(data, default_key=key.fingerprint, passphrase='adishamir', @@ -734,11 +734,13 @@ class GPGTestCase(unittest.TestCase): with self.assertRaises(UnicodeDecodeError): print("SIG=%s" % sig) - verifysig = open(sigfn, 'rb') - verification = self.gpg.verify_file(data, verifysig) + datafd.seek(0) + verification = self.gpg.verify_file(datafd, sig_file=sigfn) self.assertTrue(isinstance(verification, gnupg._parsers.Verify)) - self.assertFalse(verification.valid) + self.assertTrue(verification.valid) + + datafd.close() if os.path.isfile(sigfn): os.unlink(sigfn) diff --git a/patches/0001-Make-_open_subprocess-argument-more-explicit-in-_han.patch b/patches/0001-Make-_open_subprocess-argument-more-explicit-in-_han.patch deleted file mode 100644 index a90a4af..0000000 --- a/patches/0001-Make-_open_subprocess-argument-more-explicit-in-_han.patch +++ /dev/null @@ -1,30 +0,0 @@ -From cfcb84db5452b1fbc801ca85f2f70015660f3132 Mon Sep 17 00:00:00 2001 -From: Lunar -Date: Wed, 6 Mar 2013 18:39:34 +0100 -Subject: [PATCH] Make _open_subprocess argument more explicit in _handle_io - -The previous way worked out, but was really harder to understand. ---- - gnupg.py | 6 +++++- - 1 files changed, 5 insertions(+), 1 deletions(-) - -diff --git a/gnupg.py b/gnupg.py -index 4a73164..479e6dd 100644 ---- a/gnupg.py -+++ b/gnupg.py -@@ -984,7 +984,11 @@ class GPG(object): - """ - Handle a call to GPG - pass input data, collect output data. - """ -- p = self._open_subprocess(args, passphrase is not None) -+ if passphrase is not None: -+ ask_passphrase = True -+ else: -+ ask_passphrase = False -+ p = self._open_subprocess(args, ask_passphrase) - if not binary: - stdin = codecs.getwriter(self.encoding)(p.stdin) - else: --- -1.7.2.5 - diff --git a/patches/README.md b/patches/README.md deleted file mode 100644 index e409f4c..0000000 --- a/patches/README.md +++ /dev/null @@ -1,23 +0,0 @@ -This patches folder is managed by quilt, which is a tool for automatic patch -application and removal. To use quilt with the patches in this directory, -navigate to the top level directory of this repository, and do: - - $ quilt setup patches/series - -To add an externally created patch (in other words, one created with ```diff ---git``` or ```git diff```), place that .patch or .diff file in this directory, -and do: - - $ quilt import patches/ - -Then, to apply the new patch, do: - - $ quilt push - -Removing patches from the stack can be done with: - - $ quilt pop - -Please see the man quilt(1) for more information on adding and importing new -patches. The debian package maintainer guides also have chapters on quilt -usage. diff --git a/patches/series b/patches/series deleted file mode 100644 index 572d722..0000000 --- a/patches/series +++ /dev/null @@ -1 +0,0 @@ -0001-Make-_open_subprocess-argument-more-explicit-in-_han.patch diff --git a/scripts/distribute_setup.py b/scripts/distribute_setup.py deleted file mode 100755 index 6899f83..0000000 --- a/scripts/distribute_setup.py +++ /dev/null @@ -1,558 +0,0 @@ -#!/usr/bin/env python -"""Bootstrap distribute installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from distribute_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. - -This file was taken from http://nightly.ziade.org/distribute_setup.py -on 2013-05-27. -""" -import os -import shutil -import sys -import time -import fnmatch -import tempfile -import tarfile -import optparse - -from distutils import log - -try: - from site import USER_SITE -except ImportError: - USER_SITE = None - -try: - import subprocess - - def _python_cmd(*args): - args = (sys.executable,) + args - return subprocess.call(args) == 0 - -except ImportError: - # will be used for python 2.3 - def _python_cmd(*args): - args = (sys.executable,) + args - # quoting arguments if windows - if sys.platform == 'win32': - def quote(arg): - if ' ' in arg: - return '"%s"' % arg - return arg - args = [quote(arg) for arg in args] - return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 - -DEFAULT_VERSION = "0.6.44" -DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" -SETUPTOOLS_FAKED_VERSION = "0.6c11" - -SETUPTOOLS_PKG_INFO = """\ -Metadata-Version: 1.0 -Name: setuptools -Version: %s -Summary: xxxx -Home-page: xxx -Author: xxx -Author-email: xxx -License: xxx -Description: xxx -""" % SETUPTOOLS_FAKED_VERSION - - -def _install(tarball, install_args=()): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # installing - log.warn('Installing Distribute') - if not _python_cmd('setup.py', 'install', *install_args): - log.warn('Something went wrong during the installation.') - log.warn('See the error message above.') - # exitcode will be 2 - return 2 - finally: - os.chdir(old_wd) - shutil.rmtree(tmpdir) - - -def _build_egg(egg, tarball, to_dir): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # building an egg - log.warn('Building a Distribute egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) - - finally: - os.chdir(old_wd) - shutil.rmtree(tmpdir) - # returning the result - log.warn(egg) - if not os.path.exists(egg): - raise IOError('Could not build the egg.') - - -def _do_download(version, download_base, to_dir, download_delay): - egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' - % (version, sys.version_info[0], sys.version_info[1])) - if not os.path.exists(egg): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) - _build_egg(egg, tarball, to_dir) - sys.path.insert(0, egg) - import setuptools - setuptools.bootstrap_install_from = egg - - -def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15, no_fake=True): - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - was_imported = 'pkg_resources' in sys.modules or \ - 'setuptools' in sys.modules - try: - try: - import pkg_resources - - # Setuptools 0.7b and later is a suitable (and preferable) - # substitute for any Distribute version. - try: - pkg_resources.require("setuptools>=0.7b") - return - except pkg_resources.DistributionNotFound: - pass - - if not hasattr(pkg_resources, '_distribute'): - if not no_fake: - _fake_setuptools() - raise ImportError - except ImportError: - return _do_download(version, download_base, to_dir, download_delay) - try: - pkg_resources.require("distribute>=" + version) - return - except pkg_resources.VersionConflict: - e = sys.exc_info()[1] - if was_imported: - sys.stderr.write( - "The required version of distribute (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U distribute'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) - except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) - finally: - if not no_fake: - _create_fake_setuptools_pkg_info(to_dir) - - -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15): - """Download distribute from a specified location and return its filename - - `version` should be a valid distribute version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download - attempt. - """ - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - try: - from urllib.request import urlopen - except ImportError: - from urllib2 import urlopen - tgz_name = "distribute-%s.tar.gz" % version - url = download_base + tgz_name - saveto = os.path.join(to_dir, tgz_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - log.warn("Downloading %s", url) - src = urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = src.read() - dst = open(saveto, "wb") - dst.write(data) - finally: - if src: - src.close() - if dst: - dst.close() - return os.path.realpath(saveto) - - -def _no_sandbox(function): - def __no_sandbox(*args, **kw): - try: - from setuptools.sandbox import DirectorySandbox - if not hasattr(DirectorySandbox, '_old'): - def violation(*args): - pass - DirectorySandbox._old = DirectorySandbox._violation - DirectorySandbox._violation = violation - patched = True - else: - patched = False - except ImportError: - patched = False - - try: - return function(*args, **kw) - finally: - if patched: - DirectorySandbox._violation = DirectorySandbox._old - del DirectorySandbox._old - - return __no_sandbox - - -def _patch_file(path, content): - """Will backup the file then patch it""" - f = open(path) - existing_content = f.read() - f.close() - if existing_content == content: - # already patched - log.warn('Already patched.') - return False - log.warn('Patching...') - _rename_path(path) - f = open(path, 'w') - try: - f.write(content) - finally: - f.close() - return True - -_patch_file = _no_sandbox(_patch_file) - - -def _same_content(path, content): - f = open(path) - existing_content = f.read() - f.close() - return existing_content == content - - -def _rename_path(path): - new_name = path + '.OLD.%s' % time.time() - log.warn('Renaming %s to %s', path, new_name) - os.rename(path, new_name) - return new_name - - -def _remove_flat_installation(placeholder): - if not os.path.isdir(placeholder): - log.warn('Unkown installation at %s', placeholder) - return False - found = False - for file in os.listdir(placeholder): - if fnmatch.fnmatch(file, 'setuptools*.egg-info'): - found = True - break - if not found: - log.warn('Could not locate setuptools*.egg-info') - return - - log.warn('Moving elements out of the way...') - pkg_info = os.path.join(placeholder, file) - if os.path.isdir(pkg_info): - patched = _patch_egg_dir(pkg_info) - else: - patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) - - if not patched: - log.warn('%s already patched.', pkg_info) - return False - # now let's move the files out of the way - for element in ('setuptools', 'pkg_resources.py', 'site.py'): - element = os.path.join(placeholder, element) - if os.path.exists(element): - _rename_path(element) - else: - log.warn('Could not find the %s element of the ' - 'Setuptools distribution', element) - return True - -_remove_flat_installation = _no_sandbox(_remove_flat_installation) - - -def _after_install(dist): - log.warn('After install bootstrap.') - placeholder = dist.get_command_obj('install').install_purelib - _create_fake_setuptools_pkg_info(placeholder) - - -def _create_fake_setuptools_pkg_info(placeholder): - if not placeholder or not os.path.exists(placeholder): - log.warn('Could not find the install location') - return - pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) - setuptools_file = 'setuptools-%s-py%s.egg-info' % \ - (SETUPTOOLS_FAKED_VERSION, pyver) - pkg_info = os.path.join(placeholder, setuptools_file) - if os.path.exists(pkg_info): - log.warn('%s already exists', pkg_info) - return - - log.warn('Creating %s', pkg_info) - try: - f = open(pkg_info, 'w') - except EnvironmentError: - log.warn("Don't have permissions to write %s, skipping", pkg_info) - return - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - - pth_file = os.path.join(placeholder, 'setuptools.pth') - log.warn('Creating %s', pth_file) - f = open(pth_file, 'w') - try: - f.write(os.path.join(os.curdir, setuptools_file)) - finally: - f.close() - -_create_fake_setuptools_pkg_info = _no_sandbox( - _create_fake_setuptools_pkg_info -) - - -def _patch_egg_dir(path): - # let's check if it's already patched - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - if os.path.exists(pkg_info): - if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): - log.warn('%s already patched.', pkg_info) - return False - _rename_path(path) - os.mkdir(path) - os.mkdir(os.path.join(path, 'EGG-INFO')) - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - return True - -_patch_egg_dir = _no_sandbox(_patch_egg_dir) - - -def _before_install(): - log.warn('Before install bootstrap.') - _fake_setuptools() - - -def _under_prefix(location): - if 'install' not in sys.argv: - return True - args = sys.argv[sys.argv.index('install') + 1:] - for index, arg in enumerate(args): - for option in ('--root', '--prefix'): - if arg.startswith('%s=' % option): - top_dir = arg.split('root=')[-1] - return location.startswith(top_dir) - elif arg == option: - if len(args) > index: - top_dir = args[index + 1] - return location.startswith(top_dir) - if arg == '--user' and USER_SITE is not None: - return location.startswith(USER_SITE) - return True - - -def _fake_setuptools(): - log.warn('Scanning installed packages') - try: - import pkg_resources - except ImportError: - # we're cool - log.warn('Setuptools or Distribute does not seem to be installed.') - return - ws = pkg_resources.working_set - try: - setuptools_dist = ws.find( - pkg_resources.Requirement.parse('setuptools', replacement=False) - ) - except TypeError: - # old distribute API - setuptools_dist = ws.find( - pkg_resources.Requirement.parse('setuptools') - ) - - if setuptools_dist is None: - log.warn('No setuptools distribution found') - return - # detecting if it was already faked - setuptools_location = setuptools_dist.location - log.warn('Setuptools installation detected at %s', setuptools_location) - - # if --root or --preix was provided, and if - # setuptools is not located in them, we don't patch it - if not _under_prefix(setuptools_location): - log.warn('Not patching, --root or --prefix is installing Distribute' - ' in another location') - return - - # let's see if its an egg - if not setuptools_location.endswith('.egg'): - log.warn('Non-egg installation') - res = _remove_flat_installation(setuptools_location) - if not res: - return - else: - log.warn('Egg installation') - pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') - if (os.path.exists(pkg_info) and - _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): - log.warn('Already patched.') - return - log.warn('Patching...') - # let's create a fake egg replacing setuptools one - res = _patch_egg_dir(setuptools_location) - if not res: - return - log.warn('Patching complete.') - _relaunch() - - -def _relaunch(): - log.warn('Relaunching...') - # we have to relaunch the process - # pip marker to avoid a relaunch bug - _cmd1 = ['-c', 'install', '--single-version-externally-managed'] - _cmd2 = ['-c', 'install', '--record'] - if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: - sys.argv[0] = 'setup.py' - args = [sys.executable] + sys.argv - sys.exit(subprocess.call(args)) - - -def _extractall(self, path=".", members=None): - """Extract all members from the archive to the current working - directory and set owner, modification time and permissions on - directories afterwards. `path' specifies a different directory - to extract to. `members' is optional and must be a subset of the - list returned by getmembers(). - """ - import copy - import operator - from tarfile import ExtractError - directories = [] - - if members is None: - members = self - - for tarinfo in members: - if tarinfo.isdir(): - # Extract directories with a safe mode. - directories.append(tarinfo) - tarinfo = copy.copy(tarinfo) - tarinfo.mode = 448 # decimal for oct 0700 - self.extract(tarinfo, path) - - # Reverse sort directories. - if sys.version_info < (2, 4): - def sorter(dir1, dir2): - return cmp(dir1.name, dir2.name) - directories.sort(sorter) - directories.reverse() - else: - directories.sort(key=operator.attrgetter('name'), reverse=True) - - # Set correct owner, mtime and filemode on directories. - for tarinfo in directories: - dirpath = os.path.join(path, tarinfo.name) - try: - self.chown(tarinfo, dirpath) - self.utime(tarinfo, dirpath) - self.chmod(tarinfo, dirpath) - except ExtractError: - e = sys.exc_info()[1] - if self.errorlevel > 1: - raise - else: - self._dbg(1, "tarfile: %s" % e) - - -def _build_install_args(options): - """ - Build the arguments to 'python setup.py install' on the distribute package - """ - install_args = [] - if options.user_install: - if sys.version_info < (2, 6): - log.warn("--user requires Python 2.6 or later") - raise SystemExit(1) - install_args.append('--user') - return install_args - -def _parse_args(): - """ - Parse the command line for options - """ - parser = optparse.OptionParser() - parser.add_option( - '--user', dest='user_install', action='store_true', default=False, - help='install in user site package (requires Python 2.6 or later)') - parser.add_option( - '--download-base', dest='download_base', metavar="URL", - default=DEFAULT_URL, - help='alternative URL from where to download the distribute package') - options, args = parser.parse_args() - # positional arguments are ignored - return options - -def main(version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - options = _parse_args() - tarball = download_setuptools(download_base=options.download_base) - return _install(tarball, _build_install_args(options)) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/get-pip.py b/scripts/get-pip.py deleted file mode 100755 index 9fd5b30..0000000 --- a/scripts/get-pip.py +++ /dev/null @@ -1,1153 +0,0 @@ -#! /usr/bin/env python - -sources = """ -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 -""" - -import os -import sys -import base64 -import zlib -import tempfile -import shutil - - -def unpack(sources): - temp_dir = tempfile.mkdtemp('-scratchdir', 'unpacker-') - for package, content in sources.items(): - filepath = package.split(".") - dirpath = os.sep.join(filepath[:-1]) - packagedir = os.path.join(temp_dir, dirpath) - if not os.path.isdir(packagedir): - os.makedirs(packagedir) - mod = open(os.path.join(packagedir, "%s.py" % filepath[-1]), 'wb') - try: - mod.write(content.encode("ascii")) - finally: - mod.close() - return temp_dir - -if __name__ == "__main__": - if sys.version_info >= (3, 0): - exec("def do_exec(co, loc): exec(co, loc)\n") - import pickle - sources = sources.encode("ascii") # ensure bytes - sources = pickle.loads(zlib.decompress(base64.decodebytes(sources))) - else: - import cPickle as pickle - exec("def do_exec(co, loc): exec co in loc\n") - sources = pickle.loads(zlib.decompress(base64.decodestring(sources))) - - try: - temp_dir = unpack(sources) - sys.path.insert(0, temp_dir) - - entry = """ -import sys -try: - import setuptools - import pkg_resources -except ImportError: - raise SystemExit("An error occured while trying to run %s. Make sure " - "you have setuptools or distribute installed." % __file__) -import pip -pip.bootstrap() -""" - do_exec(entry, locals()) - finally: - shutil.rmtree(temp_dir) diff --git a/scripts/make-dev-virtualenv.sh b/scripts/make-dev-virtualenv.sh deleted file mode 100755 index aa72a3c..0000000 --- a/scripts/make-dev-virtualenv.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 -*- -# -# This file is part of python-gnupg, a Python interface to GnuPG. -# Copyright © 2013 Isis Lovecruft, 0xA3ADB67A2CDB8B35 -# © 2013 Andrej B. -# © 2013 LEAP Encryption Access Project -# © 2008-2012 Vinay Sajip -# © 2005 Steve Traugott -# © 2004 A.M. Kuchling -# -# This program 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. -# -# This program 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 included LICENSE file for details. - -project=python-gnupg -VENV=$(which virtualenv) -WRPR=$(which virtualenvwrapper.sh) - - -if ! test -n "$VENV" ; then - printf "Couldn't find virtualenv. Are you sure it's installed?" - exit 1 -fi - -if ! test -n "$WRPR"; then - printf "Couldn't find virtualenvwrapper. Are you sure it's installed?" - exit 1 -fi - -test -r "$WRPR" && . $WRPR -okay=$? - -if test "$okay" -eq 0 ; then - printf "Using %s as WORKON_HOME for the new virtualenv...\n" $PWD - printf "What should the name of the new virtualenv be? (default: '%s')\n" $project - read -p"Name for this virtualenv?: " name - if test -z "$name"; then - name="$project" - fi - printf "Using '$name' as our project's name..." - printf "Creating virtualenv..." - mkvirtualenv -a "$PWD" --no-site-packages --unzip-setuptools \ - --distribute --prompt="(gnupg)" "$name" - exit $? -else - printf "Something went wrong..." - printf "Exit code %d from mkvirtualenv." "$okay" - exit $okay -fi diff --git a/scripts/pep8.py b/scripts/pep8.py deleted file mode 100644 index 3539725..0000000 --- a/scripts/pep8.py +++ /dev/null @@ -1,1937 +0,0 @@ -#!/usr/bin/env python -# pep8.py - Check Python source code formatting, according to PEP 8 -# Copyright (C) 2006 Johann C. Rocholl -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -r""" -Check Python source code formatting, according to PEP 8: -http://www.python.org/dev/peps/pep-0008/ - -For usage and a list of options, try this: -$ python pep8.py -h - -This program and its regression test suite live here: -http://github.com/jcrocholl/pep8 - -Groups of errors and warnings: -E errors -W warnings -100 indentation -200 whitespace -300 blank lines -400 imports -500 line length -600 deprecation -700 statements -900 syntax error - -You can add checks to this program by writing plugins. Each plugin is -a simple function that is called for each line of source code, either -physical or logical. - -Physical line: -- Raw line of text from the input file. - -Logical line: -- Multi-line statements converted to a single line. -- Stripped left and right. -- Contents of strings replaced with 'xxx' of same length. -- Comments removed. - -The check function requests physical or logical lines by the name of -the first argument: - -def maximum_line_length(physical_line) -def extraneous_whitespace(logical_line) -def blank_lines(logical_line, blank_lines, indent_level, line_number) - -The last example above demonstrates how check plugins can request -additional information with extra arguments. All attributes of the -Checker object are available. Some examples: - -lines: a list of the raw lines from the input file -tokens: the tokens that contribute to this logical line -line_number: line number in the input file -blank_lines: blank lines before this one -indent_char: first indentation character in this file (' ' or '\t') -indent_level: indentation (with tabs expanded to multiples of 8) -previous_indent_level: indentation on previous line -previous_logical: previous logical line - -The docstring of each check function shall be the relevant part of -text from PEP 8. It is printed if the user enables --show-pep8. -Several docstrings contain examples directly from the PEP 8 document. - -Okay: spam(ham[1], {eggs: 2}) -E201: spam( ham[1], {eggs: 2}) - -These examples are verified automatically when pep8.py is run with the ---doctest option. You can add examples for your own check functions. -The format is simple: "Okay" or error/warning code followed by colon -and space, the rest of the line is example source code. If you put 'r' -before the docstring, you can use \n for newline, \t for tab and \s -for space. - -""" - -__version__ = '1.3.4a0' - -import os -import sys -import re -import time -import inspect -import keyword -import tokenize -from optparse import OptionParser -from fnmatch import fnmatch -try: - from configparser import RawConfigParser - from io import TextIOWrapper -except ImportError: - from ConfigParser import RawConfigParser - -DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git' -DEFAULT_IGNORE = 'E24' -if sys.platform == 'win32': - DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') -else: - DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or - os.path.expanduser('~/.config'), 'pep8') -MAX_LINE_LENGTH = 79 -REPORT_FORMAT = { - 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', - 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', -} - - -SINGLETONS = frozenset(['False', 'None', 'True']) -KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS -BINARY_OPERATORS = frozenset([ - '**=', '*=', '+=', '-=', '!=', '<>', - '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=', - '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<']) -UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) -OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS -WHITESPACE = frozenset(' \t') -SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE, - tokenize.INDENT, tokenize.DEDENT]) -BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] - -INDENT_REGEX = re.compile(r'([ \t]*)') -RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)') -RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,\s*\w+\s*,\s*\w+') -SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)') -ERRORCODE_REGEX = re.compile(r'[EW]\d{3}') -DOCSTRING_REGEX = re.compile(r'u?r?["\']') -EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') -WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') -COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)') -COMPARE_TYPE_REGEX = re.compile(r'([=!]=|is|is\s+not)\s*type(?:s\.(\w+)Type' - r'|\(\s*(\(\s*\)|[^)]*[^ )])\s*\))') -KEYWORD_REGEX = re.compile(r'(?:[^\s])(\s*)\b(?:%s)\b(\s*)' % - r'|'.join(KEYWORDS)) -OPERATOR_REGEX = re.compile(r'(?:[^\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') -LAMBDA_REGEX = re.compile(r'\blambda\b') -HUNK_REGEX = re.compile(r'^@@ -\d+,\d+ \+(\d+),(\d+) @@.*$') - -# Work around Python < 2.6 behaviour, which does not generate NL after -# a comment which is on a line by itself. -COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' - - -############################################################################## -# Plugins (check functions) for physical lines -############################################################################## - - -def tabs_or_spaces(physical_line, indent_char): - r""" - Never mix tabs and spaces. - - The most popular way of indenting Python is with spaces only. The - second-most popular way is with tabs only. Code indented with a mixture - of tabs and spaces should be converted to using spaces exclusively. When - invoking the Python command line interpreter with the -t option, it issues - warnings about code that illegally mixes tabs and spaces. When using -tt - these warnings become errors. These options are highly recommended! - - Okay: if a == 0:\n a = 1\n b = 1 - E101: if a == 0:\n a = 1\n\tb = 1 - """ - indent = INDENT_REGEX.match(physical_line).group(1) - for offset, char in enumerate(indent): - if char != indent_char: - return offset, "E101 indentation contains mixed spaces and tabs" - - -def tabs_obsolete(physical_line): - r""" - For new projects, spaces-only are strongly recommended over tabs. Most - editors have features that make this easy to do. - - Okay: if True:\n return - W191: if True:\n\treturn - """ - indent = INDENT_REGEX.match(physical_line).group(1) - if '\t' in indent: - return indent.index('\t'), "W191 indentation contains tabs" - - -def trailing_whitespace(physical_line): - r""" - JCR: Trailing whitespace is superfluous. - FBM: Except when it occurs as part of a blank line (i.e. the line is - nothing but whitespace). According to Python docs[1] a line with only - whitespace is considered a blank line, and is to be ignored. However, - matching a blank line to its indentation level avoids mistakenly - terminating a multi-line statement (e.g. class declaration) when - pasting code into the standard Python interpreter. - - [1] http://docs.python.org/reference/lexical_analysis.html#blank-lines - - The warning returned varies on whether the line itself is blank, for easier - filtering for those who want to indent their blank lines. - - Okay: spam(1) - W291: spam(1)\s - W293: class Foo(object):\n \n bang = 12 - """ - physical_line = physical_line.rstrip('\n') # chr(10), newline - physical_line = physical_line.rstrip('\r') # chr(13), carriage return - physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L - stripped = physical_line.rstrip(' \t\v') - if physical_line != stripped: - if stripped: - return len(stripped), "W291 trailing whitespace" - else: - return 0, "W293 blank line contains whitespace" - - -def trailing_blank_lines(physical_line, lines, line_number): - r""" - JCR: Trailing blank lines are superfluous. - - Okay: spam(1) - W391: spam(1)\n - """ - if not physical_line.rstrip() and line_number == len(lines): - return 0, "W391 blank line at end of file" - - -def missing_newline(physical_line): - """ - JCR: The last line should have a newline. - - Reports warning W292. - """ - if physical_line.rstrip() == physical_line: - return len(physical_line), "W292 no newline at end of file" - - -def maximum_line_length(physical_line, max_line_length): - """ - Limit all lines to a maximum of 79 characters. - - There are still many devices around that are limited to 80 character - lines; plus, limiting windows to 80 characters makes it possible to have - several windows side-by-side. The default wrapping on such devices looks - ugly. Therefore, please limit all lines to a maximum of 79 characters. - For flowing long blocks of text (docstrings or comments), limiting the - length to 72 characters is recommended. - - Reports error E501. - """ - line = physical_line.rstrip() - length = len(line) - if length > max_line_length: - if hasattr(line, 'decode'): # Python 2 - # The line could contain multi-byte characters - try: - length = len(line.decode('utf-8')) - except UnicodeError: - pass - if length > max_line_length: - return (max_line_length, "E501 line too long " - "(%d > %d characters)" % (length, max_line_length)) - - -############################################################################## -# Plugins (check functions) for logical lines -############################################################################## - - -def blank_lines(logical_line, blank_lines, indent_level, line_number, - previous_logical, previous_indent_level): - r""" - Separate top-level function and class definitions with two blank lines. - - Method definitions inside a class are separated by a single blank line. - - Extra blank lines may be used (sparingly) to separate groups of related - functions. Blank lines may be omitted between a bunch of related - one-liners (e.g. a set of dummy implementations). - - Use blank lines in functions, sparingly, to indicate logical sections. - - Okay: def a():\n pass\n\n\ndef b():\n pass - Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass - - E301: class Foo:\n b = 0\n def bar():\n pass - E302: def a():\n pass\n\ndef b(n):\n pass - E303: def a():\n pass\n\n\n\ndef b(n):\n pass - E303: def a():\n\n\n\n pass - E304: @decorator\n\ndef a():\n pass - """ - if line_number == 1: - return # Don't expect blank lines before the first line - if previous_logical.startswith('@'): - if blank_lines: - yield 0, "E304 blank lines found after function decorator" - elif blank_lines > 2 or (indent_level and blank_lines == 2): - yield 0, "E303 too many blank lines (%d)" % blank_lines - elif logical_line.startswith(('def ', 'class ', '@')): - if indent_level: - if not (blank_lines or previous_indent_level < indent_level or - DOCSTRING_REGEX.match(previous_logical)): - yield 0, "E301 expected 1 blank line, found 0" - elif blank_lines != 2: - yield 0, "E302 expected 2 blank lines, found %d" % blank_lines - - -def extraneous_whitespace(logical_line): - """ - Avoid extraneous whitespace in the following situations: - - - Immediately inside parentheses, brackets or braces. - - - Immediately before a comma, semicolon, or colon. - - Okay: spam(ham[1], {eggs: 2}) - E201: spam( ham[1], {eggs: 2}) - E201: spam(ham[ 1], {eggs: 2}) - E201: spam(ham[1], { eggs: 2}) - E202: spam(ham[1], {eggs: 2} ) - E202: spam(ham[1 ], {eggs: 2}) - E202: spam(ham[1], {eggs: 2 }) - - E203: if x == 4: print x, y; x, y = y , x - E203: if x == 4: print x, y ; x, y = y, x - E203: if x == 4 : print x, y; x, y = y, x - """ - line = logical_line - for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): - text = match.group() - char = text.strip() - found = match.start() - if text == char + ' ': - # assert char in '([{' - yield found + 1, "E201 whitespace after '%s'" % char - elif line[found - 1] != ',': - code = ('E202' if char in '}])' else 'E203') # if char in ',;:' - yield found, "%s whitespace before '%s'" % (code, char) - - -def whitespace_around_keywords(logical_line): - r""" - Avoid extraneous whitespace around keywords. - - Okay: True and False - E271: True and False - E272: True and False - E273: True and\tFalse - E274: True\tand False - """ - for match in KEYWORD_REGEX.finditer(logical_line): - before, after = match.groups() - - if '\t' in before: - yield match.start(1), "E274 tab before keyword" - elif len(before) > 1: - yield match.start(1), "E272 multiple spaces before keyword" - - if '\t' in after: - yield match.start(2), "E273 tab after keyword" - elif len(after) > 1: - yield match.start(2), "E271 multiple spaces after keyword" - - -def missing_whitespace(logical_line): - """ - JCR: Each comma, semicolon or colon should be followed by whitespace. - - Okay: [a, b] - Okay: (3,) - Okay: a[1:4] - Okay: a[:4] - Okay: a[1:] - Okay: a[1:4:2] - E231: ['a','b'] - E231: foo(bar,baz) - """ - line = logical_line - for index in range(len(line) - 1): - char = line[index] - if char in ',;:' and line[index + 1] not in WHITESPACE: - before = line[:index] - if char == ':' and before.count('[') > before.count(']'): - continue # Slice syntax, no space required - if char == ',' and line[index + 1] == ')': - continue # Allow tuple with only one element: (3,) - yield index, "E231 missing whitespace after '%s'" % char - - -def indentation(logical_line, previous_logical, indent_char, - indent_level, previous_indent_level): - r""" - Use 4 spaces per indentation level. - - For really old code that you don't want to mess up, you can continue to - use 8-space tabs. - - Okay: a = 1 - Okay: if a == 0:\n a = 1 - E111: a = 1 - - Okay: for item in items:\n pass - E112: for item in items:\npass - - Okay: a = 1\nb = 2 - E113: a = 1\n b = 2 - """ - if indent_char == ' ' and indent_level % 4: - yield 0, "E111 indentation is not a multiple of four" - indent_expect = previous_logical.endswith(':') - if indent_expect and indent_level <= previous_indent_level: - yield 0, "E112 expected an indented block" - if indent_level > previous_indent_level and not indent_expect: - yield 0, "E113 unexpected indentation" - - -def continuation_line_indentation(logical_line, tokens, indent_level, verbose): - r""" - Continuation lines should align wrapped elements either vertically using - Python's implicit line joining inside parentheses, brackets and braces, or - using a hanging indent. - - When using a hanging indent the following considerations should be applied: - - - there should be no arguments on the first line, and - - - further indentation should be used to clearly distinguish itself as a - continuation line. - - Okay: a = (\n) - E123: a = (\n ) - - Okay: a = (\n 42) - E121: a = (\n 42) - E122: a = (\n42) - E123: a = (\n 42\n ) - E124: a = (24,\n 42\n) - E125: if (a or\n b):\n pass - E126: a = (\n 42) - E127: a = (24,\n 42) - E128: a = (24,\n 42) - """ - first_row = tokens[0][2][0] - nrows = 1 + tokens[-1][2][0] - first_row - if nrows == 1: - return - - # indent_next tells us whether the next block is indented; assuming - # that it is indented by 4 spaces, then we should not allow 4-space - # indents on the final continuation line; in turn, some other - # indents are allowed to have an extra 4 spaces. - indent_next = logical_line.endswith(':') - - row = depth = 0 - # remember how many brackets were opened on each line - parens = [0] * nrows - # relative indents of physical lines - rel_indent = [0] * nrows - # visual indents - indent = [indent_level] - indent_chances = {} - last_indent = tokens[0][2] - if verbose >= 3: - print(">>> " + tokens[0][4].rstrip()) - - for token_type, text, start, end, line in tokens: - newline = row < start[0] - first_row - if newline: - row = start[0] - first_row - newline = (not last_token_multiline and - token_type not in (tokenize.NL, tokenize.NEWLINE)) - - if newline: - # this is the beginning of a continuation line. - last_indent = start - if verbose >= 3: - print("... " + line.rstrip()) - - # record the initial indent. - rel_indent[row] = start[1] - indent_level - - if depth: - # a bracket expression in a continuation line. - # find the line that it was opened on - for open_row in range(row - 1, -1, -1): - if parens[open_row]: - break - else: - # an unbracketed continuation line (ie, backslash) - open_row = 0 - hang = rel_indent[row] - rel_indent[open_row] - visual_indent = indent_chances.get(start[1]) - - if token_type == tokenize.OP and text in ']})': - # this line starts with a closing bracket - if indent[depth]: - if start[1] != indent[depth]: - yield (start, 'E124 closing bracket does not match ' - 'visual indentation') - elif hang: - yield (start, 'E123 closing bracket does not match ' - 'indentation of opening bracket\'s line') - elif visual_indent is True: - # visual indent is verified - if not indent[depth]: - indent[depth] = start[1] - elif visual_indent in (text, str): - # ignore token lined up with matching one from a previous line - pass - elif indent[depth] and start[1] < indent[depth]: - # visual indent is broken - yield (start, 'E128 continuation line ' - 'under-indented for visual indent') - elif hang == 4 or (indent_next and rel_indent[row] == 8): - # hanging indent is verified - pass - else: - # indent is broken - if hang <= 0: - error = 'E122', 'missing indentation or outdented' - elif indent[depth]: - error = 'E127', 'over-indented for visual indent' - elif hang % 4: - error = 'E121', 'indentation is not a multiple of four' - else: - error = 'E126', 'over-indented for hanging indent' - yield start, "%s continuation line %s" % error - - # look for visual indenting - if (parens[row] and token_type not in (tokenize.NL, tokenize.COMMENT) - and not indent[depth]): - indent[depth] = start[1] - indent_chances[start[1]] = True - if verbose >= 4: - print("bracket depth %s indent to %s" % (depth, start[1])) - # deal with implicit string concatenation - elif token_type == tokenize.STRING or text in ('u', 'ur', 'b', 'br'): - indent_chances[start[1]] = str - - # keep track of bracket depth - if token_type == tokenize.OP: - if text in '([{': - depth += 1 - indent.append(0) - parens[row] += 1 - if verbose >= 4: - print("bracket depth %s seen, col %s, visual min = %s" % - (depth, start[1], indent[depth])) - elif text in ')]}' and depth > 0: - # parent indents should not be more than this one - prev_indent = indent.pop() or last_indent[1] - for d in range(depth): - if indent[d] > prev_indent: - indent[d] = 0 - for ind in list(indent_chances): - if ind >= prev_indent: - del indent_chances[ind] - depth -= 1 - if depth: - indent_chances[indent[depth]] = True - for idx in range(row, -1, -1): - if parens[idx]: - parens[idx] -= 1 - break - assert len(indent) == depth + 1 - if start[1] not in indent_chances: - # allow to line up tokens - indent_chances[start[1]] = text - - last_token_multiline = (start[0] != end[0]) - - if indent_next and rel_indent[-1] == 4: - yield (last_indent, "E125 continuation line does not distinguish " - "itself from next logical line") - - -def whitespace_before_parameters(logical_line, tokens): - """ - Avoid extraneous whitespace in the following situations: - - - Immediately before the open parenthesis that starts the argument - list of a function call. - - - Immediately before the open parenthesis that starts an indexing or - slicing. - - Okay: spam(1) - E211: spam (1) - - Okay: dict['key'] = list[index] - E211: dict ['key'] = list[index] - E211: dict['key'] = list [index] - """ - prev_type = tokens[0][0] - prev_text = tokens[0][1] - prev_end = tokens[0][3] - for index in range(1, len(tokens)): - token_type, text, start, end, line = tokens[index] - if (token_type == tokenize.OP and - text in '([' and - start != prev_end and - (prev_type == tokenize.NAME or prev_text in '}])') and - # Syntax "class A (B):" is allowed, but avoid it - (index < 2 or tokens[index - 2][1] != 'class') and - # Allow "return (a.foo for a in range(5))" - not keyword.iskeyword(prev_text)): - yield prev_end, "E211 whitespace before '%s'" % text - prev_type = token_type - prev_text = text - prev_end = end - - -def whitespace_around_operator(logical_line): - r""" - Avoid extraneous whitespace in the following situations: - - - More than one space around an assignment (or other) operator to - align it with another. - - Okay: a = 12 + 3 - E221: a = 4 + 5 - E222: a = 4 + 5 - E223: a = 4\t+ 5 - E224: a = 4 +\t5 - """ - for match in OPERATOR_REGEX.finditer(logical_line): - before, after = match.groups() - - if '\t' in before: - yield match.start(1), "E223 tab before operator" - elif len(before) > 1: - yield match.start(1), "E221 multiple spaces before operator" - - if '\t' in after: - yield match.start(2), "E224 tab after operator" - elif len(after) > 1: - yield match.start(2), "E222 multiple spaces after operator" - - -def missing_whitespace_around_operator(logical_line, tokens): - r""" - - Always surround these binary operators with a single space on - either side: assignment (=), augmented assignment (+=, -= etc.), - comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), - Booleans (and, or, not). - - - Use spaces around arithmetic operators. - - Okay: i = i + 1 - Okay: submitted += 1 - Okay: x = x * 2 - 1 - Okay: hypot2 = x * x + y * y - Okay: c = (a + b) * (a - b) - Okay: foo(bar, key='word', *args, **kwargs) - Okay: baz(**kwargs) - Okay: negative = -1 - Okay: spam(-1) - Okay: alpha[:-i] - Okay: if not -5 < x < +5:\n pass - Okay: lambda *args, **kw: (args, kw) - - E225: i=i+1 - E225: submitted +=1 - E225: x = x*2 - 1 - E225: hypot2 = x*x + y*y - E225: c = (a+b) * (a-b) - E225: c = alpha -4 - E225: z = x **y - """ - parens = 0 - need_space = False - prev_type = tokenize.OP - prev_text = prev_end = None - for token_type, text, start, end, line in tokens: - if token_type in (tokenize.NL, tokenize.NEWLINE, tokenize.ERRORTOKEN): - # ERRORTOKEN is triggered by backticks in Python 3000 - continue - if text in ('(', 'lambda'): - parens += 1 - elif text == ')': - parens -= 1 - if need_space: - if start != prev_end: - need_space = False - elif text == '>' and prev_text in ('<', '-'): - # Tolerate the "<>" operator, even if running Python 3 - # Deal with Python 3's annotated return value "->" - pass - else: - yield prev_end, "E225 missing whitespace around operator" - need_space = False - elif token_type == tokenize.OP and prev_end is not None: - if text == '=' and parens: - # Allow keyword args or defaults: foo(bar=None). - pass - elif text in BINARY_OPERATORS: - need_space = True - elif text in UNARY_OPERATORS: - # Allow unary operators: -123, -x, +1. - # Allow argument unpacking: foo(*args, **kwargs). - if prev_type == tokenize.OP: - if prev_text in '}])': - need_space = True - elif prev_type == tokenize.NAME: - if prev_text not in KEYWORDS: - need_space = True - elif prev_type not in SKIP_TOKENS: - need_space = True - if need_space and start == prev_end: - yield prev_end, "E225 missing whitespace around operator" - need_space = False - prev_type = token_type - prev_text = text - prev_end = end - - -def whitespace_around_comma(logical_line): - r""" - Avoid extraneous whitespace in the following situations: - - - More than one space around an assignment (or other) operator to - align it with another. - - Note: these checks are disabled by default - - Okay: a = (1, 2) - E241: a = (1, 2) - E242: a = (1,\t2) - """ - line = logical_line - for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line): - found = m.start() + 1 - if '\t' in m.group(): - yield found, "E242 tab after '%s'" % m.group()[0] - else: - yield found, "E241 multiple spaces after '%s'" % m.group()[0] - - -def whitespace_around_named_parameter_equals(logical_line, tokens): - """ - Don't use spaces around the '=' sign when used to indicate a - keyword argument or a default parameter value. - - Okay: def complex(real, imag=0.0): - Okay: return magic(r=real, i=imag) - Okay: boolean(a == b) - Okay: boolean(a != b) - Okay: boolean(a <= b) - Okay: boolean(a >= b) - - E251: def complex(real, imag = 0.0): - E251: return magic(r = real, i = imag) - """ - parens = 0 - no_space = False - prev_end = None - for token_type, text, start, end, line in tokens: - if no_space: - no_space = False - if start != prev_end: - yield (prev_end, - "E251 no spaces around keyword / parameter equals") - elif token_type == tokenize.OP: - if text == '(': - parens += 1 - elif text == ')': - parens -= 1 - elif parens and text == '=': - no_space = True - if start != prev_end: - yield (prev_end, - "E251 no spaces around keyword / parameter equals") - prev_end = end - - -def whitespace_before_inline_comment(logical_line, tokens): - """ - Separate inline comments by at least two spaces. - - An inline comment is a comment on the same line as a statement. Inline - comments should be separated by at least two spaces from the statement. - They should start with a # and a single space. - - Okay: x = x + 1 # Increment x - Okay: x = x + 1 # Increment x - E261: x = x + 1 # Increment x - E262: x = x + 1 #Increment x - E262: x = x + 1 # Increment x - """ - prev_end = (0, 0) - for token_type, text, start, end, line in tokens: - if token_type == tokenize.COMMENT: - if not line[:start[1]].strip(): - continue - if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: - yield (prev_end, - "E261 at least two spaces before inline comment") - if text.startswith('# ') or not text.startswith('# '): - yield start, "E262 inline comment should start with '# '" - elif token_type != tokenize.NL: - prev_end = end - - -def imports_on_separate_lines(logical_line): - r""" - Imports should usually be on separate lines. - - Okay: import os\nimport sys - E401: import sys, os - - Okay: from subprocess import Popen, PIPE - Okay: from myclas import MyClass - Okay: from foo.bar.yourclass import YourClass - Okay: import myclass - Okay: import foo.bar.yourclass - """ - line = logical_line - if line.startswith('import '): - found = line.find(',') - if -1 < found and ';' not in line[:found]: - yield found, "E401 multiple imports on one line" - - -def compound_statements(logical_line): - r""" - Compound statements (multiple statements on the same line) are - generally discouraged. - - While sometimes it's okay to put an if/for/while with a small body - on the same line, never do this for multi-clause statements. Also - avoid folding such long lines! - - Okay: if foo == 'blah':\n do_blah_thing() - Okay: do_one() - Okay: do_two() - Okay: do_three() - - E701: if foo == 'blah': do_blah_thing() - E701: for x in lst: total += x - E701: while t < 10: t = delay() - E701: if foo == 'blah': do_blah_thing() - E701: else: do_non_blah_thing() - E701: try: something() - E701: finally: cleanup() - E701: if foo == 'blah': one(); two(); three() - - E702: do_one(); do_two(); do_three() - """ - line = logical_line - found = line.find(':') - if -1 < found < len(line) - 1: - before = line[:found] - if (before.count('{') <= before.count('}') and # {'a': 1} (dict) - before.count('[') <= before.count(']') and # [1:2] (slice) - before.count('(') <= before.count(')') and # (Python 3 annotation) - not LAMBDA_REGEX.search(before)): # lambda x: x - yield found, "E701 multiple statements on one line (colon)" - found = line.find(';') - if -1 < found: - yield found, "E702 multiple statements on one line (semicolon)" - - -def explicit_line_join(logical_line, tokens): - r""" - Avoid explicit line join between brackets. - - The preferred way of wrapping long lines is by using Python's implied line - continuation inside parentheses, brackets and braces. Long lines can be - broken over multiple lines by wrapping expressions in parentheses. These - should be used in preference to using a backslash for line continuation. - - E502: aaa = [123, \\n 123] - E502: aaa = ("bbb " \\n "ccc") - - Okay: aaa = [123,\n 123] - Okay: aaa = ("bbb "\n "ccc") - Okay: aaa = "bbb " \\n "ccc" - """ - prev_start = prev_end = parens = 0 - for token_type, text, start, end, line in tokens: - if start[0] != prev_start and parens and backslash: - yield backslash, "E502 the backslash is redundant between brackets" - if end[0] != prev_end: - if line.rstrip('\r\n').endswith('\\'): - backslash = (end[0], len(line.splitlines()[-1]) - 1) - else: - backslash = None - prev_start = prev_end = end[0] - else: - prev_start = start[0] - if token_type == tokenize.OP: - if text in '([{': - parens += 1 - elif text in ')]}': - parens -= 1 - - -def comparison_to_singleton(logical_line): - """ - Comparisons to singletons like None should always be done - with "is" or "is not", never the equality operators. - - Okay: if arg is not None: - E711: if arg != None: - E712: if arg == True: - - Also, beware of writing if x when you really mean if x is not None -- - e.g. when testing whether a variable or argument that defaults to None was - set to some other value. The other value might have a type (such as a - container) that could be false in a boolean context! - """ - match = COMPARE_SINGLETON_REGEX.search(logical_line) - if match: - same = (match.group(1) == '==') - singleton = match.group(2) - msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton) - if singleton in ('None',): - code = 'E711' - else: - code = 'E712' - nonzero = ((singleton == 'True' and same) or - (singleton == 'False' and not same)) - msg += " or 'if %scond:'" % ('' if nonzero else 'not ') - yield match.start(1), ("%s comparison to %s should be %s" % - (code, singleton, msg)) - - -def comparison_type(logical_line): - """ - Object type comparisons should always use isinstance() instead of - comparing types directly. - - Okay: if isinstance(obj, int): - E721: if type(obj) is type(1): - - When checking if an object is a string, keep in mind that it might be a - unicode string too! In Python 2.3, str and unicode have a common base - class, basestring, so you can do: - - Okay: if isinstance(obj, basestring): - Okay: if type(a1) is type(b1): - """ - match = COMPARE_TYPE_REGEX.search(logical_line) - if match: - inst = match.group(3) - if inst and isidentifier(inst) and inst not in SINGLETONS: - return # Allow comparison for types which are not obvious - yield match.start(1), "E721 do not compare types, use 'isinstance()'" - - -def python_3000_has_key(logical_line): - r""" - The {}.has_key() method will be removed in the future version of - Python. Use the 'in' operation instead. - - Okay: if "alph" in d:\n print d["alph"] - W601: assert d.has_key('alph') - """ - pos = logical_line.find('.has_key(') - if pos > -1: - yield pos, "W601 .has_key() is deprecated, use 'in'" - - -def python_3000_raise_comma(logical_line): - """ - When raising an exception, use "raise ValueError('message')" - instead of the older form "raise ValueError, 'message'". - - The paren-using form is preferred because when the exception arguments - are long or include string formatting, you don't need to use line - continuation characters thanks to the containing parentheses. The older - form will be removed in Python 3000. - - Okay: raise DummyError("Message") - W602: raise DummyError, "Message" - """ - match = RAISE_COMMA_REGEX.match(logical_line) - if match and not RERAISE_COMMA_REGEX.match(logical_line): - yield match.start(1), "W602 deprecated form of raising exception" - - -def python_3000_not_equal(logical_line): - """ - != can also be written <>, but this is an obsolete usage kept for - backwards compatibility only. New code should always use !=. - The older syntax is removed in Python 3000. - - Okay: if a != 'no': - W603: if a <> 'no': - """ - pos = logical_line.find('<>') - if pos > -1: - yield pos, "W603 '<>' is deprecated, use '!='" - - -def python_3000_backticks(logical_line): - """ - Backticks are removed in Python 3000. - Use repr() instead. - - Okay: val = repr(1 + 2) - W604: val = `1 + 2` - """ - pos = logical_line.find('`') - if pos > -1: - yield pos, "W604 backticks are deprecated, use 'repr()'" - - -############################################################################## -# Helper functions -############################################################################## - - -if '' == ''.encode(): - # Python 2: implicit encoding. - def readlines(filename): - f = open(filename) - try: - return f.readlines() - finally: - f.close() - - isidentifier = re.compile(r'[a-zA-Z_]\w*').match - stdin_get_value = sys.stdin.read -else: - # Python 3 - def readlines(filename): - f = open(filename, 'rb') - try: - coding, lines = tokenize.detect_encoding(f.readline) - f = TextIOWrapper(f, coding, line_buffering=True) - return [l.decode(coding) for l in lines] + f.readlines() - except (LookupError, SyntaxError, UnicodeError): - f.close() - # Fall back if files are improperly declared - f = open(filename, encoding='latin-1') - return f.readlines() - finally: - f.close() - - isidentifier = str.isidentifier - - def stdin_get_value(): - return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() -readlines.__doc__ = " Read the source code." - - -def expand_indent(line): - r""" - Return the amount of indentation. - Tabs are expanded to the next multiple of 8. - - >>> expand_indent(' ') - 4 - >>> expand_indent('\t') - 8 - >>> expand_indent(' \t') - 8 - >>> expand_indent(' \t') - 8 - >>> expand_indent(' \t') - 16 - """ - if '\t' not in line: - return len(line) - len(line.lstrip()) - result = 0 - for char in line: - if char == '\t': - result = result // 8 * 8 + 8 - elif char == ' ': - result += 1 - else: - break - return result - - -def mute_string(text): - """ - Replace contents with 'xxx' to prevent syntax matching. - - >>> mute_string('"abc"') - '"xxx"' - >>> mute_string("'''abc'''") - "'''xxx'''" - >>> mute_string("r'abc'") - "r'xxx'" - """ - # String modifiers (e.g. u or r) - start = text.index(text[-1]) + 1 - end = len(text) - 1 - # Triple quotes - if text[-3:] in ('"""', "'''"): - start += 2 - end -= 2 - return text[:start] + 'x' * (end - start) + text[end:] - - -def parse_udiff(diff, patterns=None, parent='.'): - rv = {} - path = nrows = None - for line in diff.splitlines(): - if nrows: - if line[:1] != '-': - nrows -= 1 - continue - if line[:3] == '@@ ': - row, nrows = [int(g) for g in HUNK_REGEX.match(line).groups()] - rv[path].update(range(row, row + nrows)) - elif line[:3] == '+++': - path = line[4:].split('\t', 1)[0] - if path[:2] == 'b/': - path = path[2:] - rv[path] = set() - return dict([(os.path.join(parent, path), rows) - for (path, rows) in rv.items() - if rows and filename_match(path, patterns)]) - - -def filename_match(filename, patterns, default=True): - """ - Check if patterns contains a pattern that matches filename. - If patterns is unspecified, this always returns True. - """ - if not patterns: - return default - return any(fnmatch(filename, pattern) for pattern in patterns) - - -############################################################################## -# Framework to run all checks -############################################################################## - - -def find_checks(argument_name): - """ - Find all globally visible functions where the first argument name - starts with argument_name. - """ - for name, function in globals().items(): - if not inspect.isfunction(function): - continue - args = inspect.getargspec(function)[0] - if args and args[0].startswith(argument_name): - codes = ERRORCODE_REGEX.findall(function.__doc__ or '') - yield name, codes, function, args - - -class Checker(object): - """ - Load a Python source file, tokenize it, check coding style. - """ - - def __init__(self, filename, lines=None, - options=None, report=None, **kwargs): - if options is None: - options = StyleGuide(kwargs).options - else: - assert not kwargs - self._io_error = None - self._physical_checks = options.physical_checks - self._logical_checks = options.logical_checks - self.max_line_length = options.max_line_length - self.verbose = options.verbose - self.filename = filename - if filename is None: - self.filename = 'stdin' - self.lines = lines or [] - elif filename == '-': - self.filename = 'stdin' - self.lines = stdin_get_value().splitlines(True) - elif lines is None: - try: - self.lines = readlines(filename) - except IOError: - exc_type, exc = sys.exc_info()[:2] - self._io_error = '%s: %s' % (exc_type.__name__, exc) - self.lines = [] - else: - self.lines = lines - self.report = report or options.report - self.report_error = self.report.error - - def readline(self): - """ - Get the next line from the input buffer. - """ - self.line_number += 1 - if self.line_number > len(self.lines): - return '' - return self.lines[self.line_number - 1] - - def readline_check_physical(self): - """ - Check and return the next physical line. This method can be - used to feed tokenize.generate_tokens. - """ - line = self.readline() - if line: - self.check_physical(line) - return line - - def run_check(self, check, argument_names): - """ - Run a check plugin. - """ - arguments = [] - for name in argument_names: - arguments.append(getattr(self, name)) - return check(*arguments) - - def check_physical(self, line): - """ - Run all physical checks on a raw input line. - """ - self.physical_line = line - if self.indent_char is None and line[:1] in WHITESPACE: - self.indent_char = line[0] - for name, check, argument_names in self._physical_checks: - result = self.run_check(check, argument_names) - if result is not None: - offset, text = result - self.report_error(self.line_number, offset, text, check) - - def build_tokens_line(self): - """ - Build a logical line from tokens. - """ - self.mapping = [] - logical = [] - length = 0 - previous = None - for token in self.tokens: - token_type, text = token[0:2] - if token_type in SKIP_TOKENS: - continue - if token_type == tokenize.STRING: - text = mute_string(text) - if previous: - end_row, end = previous[3] - start_row, start = token[2] - if end_row != start_row: # different row - prev_text = self.lines[end_row - 1][end - 1] - if prev_text == ',' or (prev_text not in '{[(' - and text not in '}])'): - logical.append(' ') - length += 1 - elif end != start: # different column - fill = self.lines[end_row - 1][end:start] - logical.append(fill) - length += len(fill) - self.mapping.append((length, token)) - logical.append(text) - length += len(text) - previous = token - self.logical_line = ''.join(logical) - assert self.logical_line.strip() == self.logical_line - - def check_logical(self): - """ - Build a line from tokens and run all logical checks on it. - """ - self.build_tokens_line() - self.report.increment_logical_line() - first_line = self.lines[self.mapping[0][1][2][0] - 1] - indent = first_line[:self.mapping[0][1][2][1]] - self.previous_indent_level = self.indent_level - self.indent_level = expand_indent(indent) - if self.verbose >= 2: - print(self.logical_line[:80].rstrip()) - for name, check, argument_names in self._logical_checks: - if self.verbose >= 4: - print(' ' + name) - for result in self.run_check(check, argument_names): - offset, text = result - if isinstance(offset, tuple): - orig_number, orig_offset = offset - else: - for token_offset, token in self.mapping: - if offset >= token_offset: - orig_number = token[2][0] - orig_offset = (token[2][1] + offset - token_offset) - self.report_error(orig_number, orig_offset, text, check) - self.previous_logical = self.logical_line - - def generate_tokens(self): - if self._io_error: - self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) - tokengen = tokenize.generate_tokens(self.readline_check_physical) - try: - for token in tokengen: - yield token - except (SyntaxError, tokenize.TokenError): - exc_type, exc = sys.exc_info()[:2] - offset = exc.args[1] - if len(offset) > 2: - offset = offset[1:3] - self.report_error(offset[0], offset[1], - 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), - self.generate_tokens) - generate_tokens.__doc__ = " Check if the syntax is valid." - - def check_all(self, expected=None, line_offset=0): - """ - Run all checks on the input file. - """ - self.report.init_file(self.filename, self.lines, expected, line_offset) - self.line_number = 0 - self.indent_char = None - self.indent_level = 0 - self.previous_logical = '' - self.tokens = [] - self.blank_lines = blank_lines_before_comment = 0 - parens = 0 - for token in self.generate_tokens(): - self.tokens.append(token) - token_type, text = token[0:2] - if self.verbose >= 3: - if token[2][0] == token[3][0]: - pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) - else: - pos = 'l.%s' % token[3][0] - print('l.%s\t%s\t%s\t%r' % - (token[2][0], pos, tokenize.tok_name[token[0]], text)) - if token_type == tokenize.OP: - if text in '([{': - parens += 1 - elif text in '}])': - parens -= 1 - elif not parens: - if token_type == tokenize.NEWLINE: - if self.blank_lines < blank_lines_before_comment: - self.blank_lines = blank_lines_before_comment - self.check_logical() - self.tokens = [] - self.blank_lines = blank_lines_before_comment = 0 - elif token_type == tokenize.NL: - if len(self.tokens) == 1: - # The physical line contains only this token. - self.blank_lines += 1 - self.tokens = [] - elif token_type == tokenize.COMMENT and len(self.tokens) == 1: - if blank_lines_before_comment < self.blank_lines: - blank_lines_before_comment = self.blank_lines - self.blank_lines = 0 - if COMMENT_WITH_NL: - # The comment also ends a physical line - self.tokens = [] - return self.report.get_file_results() - - -class BaseReport(object): - """Collect the results of the checks.""" - print_filename = False - - def __init__(self, options): - self._benchmark_keys = options.benchmark_keys - self._ignore_code = options.ignore_code - # Results - self.elapsed = 0 - self.total_errors = 0 - self.counters = dict.fromkeys(self._benchmark_keys, 0) - self.messages = {} - - def start(self): - """Start the timer.""" - self._start_time = time.time() - - def stop(self): - """Stop the timer.""" - self.elapsed = time.time() - self._start_time - - def init_file(self, filename, lines, expected, line_offset): - """Signal a new file.""" - self.filename = filename - self.lines = lines - self.expected = expected or () - self.line_offset = line_offset - self.file_errors = 0 - self.counters['files'] += 1 - self.counters['physical lines'] += len(lines) - - def increment_logical_line(self): - """Signal a new logical line.""" - self.counters['logical lines'] += 1 - - def error(self, line_number, offset, text, check): - """Report an error, according to options.""" - code = text[:4] - if self._ignore_code(code): - return - if code in self.counters: - self.counters[code] += 1 - else: - self.counters[code] = 1 - self.messages[code] = text[5:] - # Don't care about expected errors or warnings - if code in self.expected: - return - if self.print_filename and not self.file_errors: - print(self.filename) - self.file_errors += 1 - self.total_errors += 1 - return code - - def get_file_results(self): - """Return the count of errors and warnings for this file.""" - return self.file_errors - - def get_count(self, prefix=''): - """Return the total count of errors and warnings.""" - return sum([self.counters[key] - for key in self.messages if key.startswith(prefix)]) - - def get_statistics(self, prefix=''): - """ - Get statistics for message codes that start with the prefix. - - prefix='' matches all errors and warnings - prefix='E' matches all errors - prefix='W' matches all warnings - prefix='E4' matches all errors that have to do with imports - """ - return ['%-7s %s %s' % (self.counters[key], key, self.messages[key]) - for key in sorted(self.messages) if key.startswith(prefix)] - - def print_statistics(self, prefix=''): - """Print overall statistics (number of errors and warnings).""" - for line in self.get_statistics(prefix): - print(line) - - def print_benchmark(self): - """Print benchmark numbers.""" - print('%-7.2f %s' % (self.elapsed, 'seconds elapsed')) - if self.elapsed: - for key in self._benchmark_keys: - print('%-7d %s per second (%d total)' % - (self.counters[key] / self.elapsed, key, - self.counters[key])) - - -class FileReport(BaseReport): - print_filename = True - - -class StandardReport(BaseReport): - """Collect and print the results of the checks.""" - - def __init__(self, options): - super(StandardReport, self).__init__(options) - self._fmt = REPORT_FORMAT.get(options.format.lower(), - options.format) - self._repeat = options.repeat - self._show_source = options.show_source - self._show_pep8 = options.show_pep8 - - def error(self, line_number, offset, text, check): - """ - Report an error, according to options. - """ - code = super(StandardReport, self).error(line_number, offset, - text, check) - if code and (self.counters[code] == 1 or self._repeat): - print(self._fmt % { - 'path': self.filename, - 'row': self.line_offset + line_number, 'col': offset + 1, - 'code': code, 'text': text[5:], - }) - if self._show_source: - if line_number > len(self.lines): - line = '' - else: - line = self.lines[line_number - 1] - print(line.rstrip()) - print(' ' * offset + '^') - if self._show_pep8: - print(check.__doc__.lstrip('\n').rstrip()) - return code - - -class DiffReport(StandardReport): - """Collect and print the results for the changed lines only.""" - - def __init__(self, options): - super(DiffReport, self).__init__(options) - self._selected = options.selected_lines - - def error(self, line_number, offset, text, check): - if line_number not in self._selected[self.filename]: - return - return super(DiffReport, self).error(line_number, offset, text, check) - - -class TestReport(StandardReport): - """Collect the results for the tests.""" - - def __init__(self, options): - options.benchmark_keys += ['test cases', 'failed tests'] - super(TestReport, self).__init__(options) - self._verbose = options.verbose - - def get_file_results(self): - # Check if the expected errors were found - label = '%s:%s:1' % (self.filename, self.line_offset) - codes = sorted(self.expected) - for code in codes: - if not self.counters.get(code): - self.file_errors += 1 - self.total_errors += 1 - print('%s: error %s not found' % (label, code)) - if self._verbose and not self.file_errors: - print('%s: passed (%s)' % - (label, ' '.join(codes) or 'Okay')) - self.counters['test cases'] += 1 - if self.file_errors: - self.counters['failed tests'] += 1 - # Reset counters - for key in set(self.counters) - set(self._benchmark_keys): - del self.counters[key] - self.messages = {} - return self.file_errors - - def print_results(self): - results = ("%(physical lines)d lines tested: %(files)d files, " - "%(test cases)d test cases%%s." % self.counters) - if self.total_errors: - print(results % ", %s failures" % self.total_errors) - else: - print(results % "") - print("Test failed." if self.total_errors else "Test passed.") - - -class StyleGuide(object): - """Initialize a PEP-8 instance with few options.""" - - def __init__(self, *args, **kwargs): - # build options from the command line - parse_argv = kwargs.pop('parse_argv', False) - config_file = kwargs.pop('config_file', None) - options, self.paths = process_options(parse_argv=parse_argv, - config_file=config_file) - if args or kwargs: - # build options from dict - options_dict = dict(*args, **kwargs) - options.__dict__.update(options_dict) - if 'paths' in options_dict: - self.paths = options_dict['paths'] - - self.runner = self.input_file - self.options = options - - if not options.reporter: - options.reporter = BaseReport if options.quiet else StandardReport - - for index, value in enumerate(options.exclude): - options.exclude[index] = value.rstrip('/') - # Ignore all checks which are not explicitly selected - options.select = tuple(options.select or ()) - options.ignore = tuple(options.ignore or options.select and ('',)) - options.benchmark_keys = BENCHMARK_KEYS[:] - options.ignore_code = self.ignore_code - options.physical_checks = self.get_checks('physical_line') - options.logical_checks = self.get_checks('logical_line') - self.init_report() - - def init_report(self, reporter=None): - """Initialize the report instance.""" - self.options.report = (reporter or self.options.reporter)(self.options) - return self.options.report - - def check_files(self, paths=None): - """Run all checks on the paths.""" - if paths is None: - paths = self.paths - report = self.options.report - runner = self.runner - report.start() - for path in paths: - if os.path.isdir(path): - self.input_dir(path) - elif not self.excluded(path): - runner(path) - report.stop() - return report - - def input_file(self, filename, lines=None, expected=None, line_offset=0): - """Run all checks on a Python source file.""" - if self.options.verbose: - print('checking %s' % filename) - fchecker = Checker(filename, lines=lines, options=self.options) - return fchecker.check_all(expected=expected, line_offset=line_offset) - - def input_dir(self, dirname): - """Check all files in this directory and all subdirectories.""" - dirname = dirname.rstrip('/') - if self.excluded(dirname): - return 0 - counters = self.options.report.counters - verbose = self.options.verbose - filepatterns = self.options.filename - runner = self.runner - for root, dirs, files in os.walk(dirname): - if verbose: - print('directory ' + root) - counters['directories'] += 1 - for subdir in sorted(dirs): - if self.excluded(subdir): - dirs.remove(subdir) - for filename in sorted(files): - # contain a pattern that matches? - if ((filename_match(filename, filepatterns) and - not self.excluded(filename))): - runner(os.path.join(root, filename)) - - def excluded(self, filename): - """ - Check if options.exclude contains a pattern that matches filename. - """ - basename = os.path.basename(filename) - return filename_match(basename, self.options.exclude, default=False) - - def ignore_code(self, code): - """ - Check if the error code should be ignored. - - If 'options.select' contains a prefix of the error code, - return False. Else, if 'options.ignore' contains a prefix of - the error code, return True. - """ - return (code.startswith(self.options.ignore) and - not code.startswith(self.options.select)) - - def get_checks(self, argument_name): - """ - Find all globally visible functions where the first argument name - starts with argument_name and which contain selected tests. - """ - checks = [] - for name, codes, function, args in find_checks(argument_name): - if any(not (code and self.ignore_code(code)) for code in codes): - checks.append((name, function, args)) - return sorted(checks) - - -def init_tests(pep8style): - """ - Initialize testing framework. - - A test file can provide many tests. Each test starts with a - declaration. This declaration is a single line starting with '#:'. - It declares codes of expected failures, separated by spaces or 'Okay' - if no failure is expected. - If the file does not contain such declaration, it should pass all - tests. If the declaration is empty, following lines are not checked, - until next declaration. - - Examples: - - * Only E224 and W701 are expected: #: E224 W701 - * Following example is conform: #: Okay - * Don't check these lines: #: - """ - report = pep8style.init_report(TestReport) - runner = pep8style.input_file - - def run_tests(filename): - """Run all the tests from a file.""" - lines = readlines(filename) + ['#:\n'] - line_offset = 0 - codes = ['Okay'] - testcase = [] - count_files = report.counters['files'] - for index, line in enumerate(lines): - if not line.startswith('#:'): - if codes: - # Collect the lines of the test case - testcase.append(line) - continue - if codes and index: - codes = [c for c in codes if c != 'Okay'] - # Run the checker - runner(filename, testcase, expected=codes, - line_offset=line_offset) - # output the real line numbers - line_offset = index + 1 - # configure the expected errors - codes = line.split()[1:] - # empty the test case buffer - del testcase[:] - report.counters['files'] = count_files + 1 - return report.counters['failed tests'] - - pep8style.runner = run_tests - - -def selftest(options): - """ - Test all check functions with test cases in docstrings. - """ - count_failed = count_all = 0 - report = BaseReport(options) - counters = report.counters - checks = options.physical_checks + options.logical_checks - for name, check, argument_names in checks: - for line in check.__doc__.splitlines(): - line = line.lstrip() - match = SELFTEST_REGEX.match(line) - if match is None: - continue - code, source = match.groups() - checker = Checker(None, options=options, report=report) - for part in source.split(r'\n'): - part = part.replace(r'\t', '\t') - part = part.replace(r'\s', ' ') - checker.lines.append(part + '\n') - checker.check_all() - error = None - if code == 'Okay': - if len(counters) > len(options.benchmark_keys): - codes = [key for key in counters - if key not in options.benchmark_keys] - error = "incorrectly found %s" % ', '.join(codes) - elif not counters.get(code): - error = "failed to find %s" % code - # Keep showing errors for multiple tests - for key in set(counters) - set(options.benchmark_keys): - del counters[key] - report.messages = {} - count_all += 1 - if not error: - if options.verbose: - print("%s: %s" % (code, source)) - else: - count_failed += 1 - print("%s: %s:" % (__file__, error)) - for line in checker.lines: - print(line.rstrip()) - return count_failed, count_all - - -def read_config(options, args, arglist, parser): - """Read both user configuration and local configuration.""" - config = RawConfigParser() - - user_conf = options.config - if user_conf and os.path.isfile(user_conf): - if options.verbose: - print('user configuration: %s' % user_conf) - config.read(user_conf) - - parent = tail = args and os.path.abspath(os.path.commonprefix(args)) - while tail: - local_conf = os.path.join(parent, '.pep8') - if os.path.isfile(local_conf): - if options.verbose: - print('local configuration: %s' % local_conf) - config.read(local_conf) - break - parent, tail = os.path.split(parent) - - if config.has_section('pep8'): - option_list = dict([(o.dest, o.type or o.action) - for o in parser.option_list]) - - # First, read the default values - new_options, _ = parser.parse_args([]) - - # Second, parse the configuration - for opt in config.options('pep8'): - if options.verbose > 1: - print(' %s = %s' % (opt, config.get('pep8', opt))) - if opt.replace('_', '-') not in parser.config_options: - print('Unknown option: \'%s\'\n not in [%s]' % - (opt, ' '.join(parser.config_options))) - sys.exit(1) - normalized_opt = opt.replace('-', '_') - opt_type = option_list[normalized_opt] - if opt_type in ('int', 'count'): - value = config.getint('pep8', opt) - elif opt_type == 'string': - value = config.get('pep8', opt) - else: - assert opt_type in ('store_true', 'store_false') - value = config.getboolean('pep8', opt) - setattr(new_options, normalized_opt, value) - - # Third, overwrite with the command-line options - options, _ = parser.parse_args(arglist, values=new_options) - - return options - - -def process_options(arglist=None, parse_argv=False, config_file=None): - """Process options passed either via arglist or via command line args.""" - if not arglist and not parse_argv: - # Don't read the command line if the module is used as a library. - arglist = [] - if config_file is True: - config_file = DEFAULT_CONFIG - parser = OptionParser(version=__version__, - usage="%prog [options] input ...") - parser.config_options = [ - 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count', - 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose'] - parser.add_option('-v', '--verbose', default=0, action='count', - help="print status messages, or debug with -vv") - parser.add_option('-q', '--quiet', default=0, action='count', - help="report only file names, or nothing with -qq") - parser.add_option('-r', '--repeat', default=True, action='store_true', - help="(obsolete) show all occurrences of the same error") - parser.add_option('--first', action='store_false', dest='repeat', - help="show first occurrence of each error") - parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, - help="exclude files or directories which match these " - "comma separated patterns (default: %default)") - parser.add_option('--filename', metavar='patterns', default='*.py', - help="when parsing directories, only check filenames " - "matching these comma separated patterns " - "(default: %default)") - parser.add_option('--select', metavar='errors', default='', - help="select errors and warnings (e.g. E,W6)") - parser.add_option('--ignore', metavar='errors', default='', - help="skip errors and warnings (e.g. E4,W)") - parser.add_option('--show-source', action='store_true', - help="show source code for each error") - parser.add_option('--show-pep8', action='store_true', - help="show text of PEP 8 for each error " - "(implies --first)") - parser.add_option('--statistics', action='store_true', - help="count errors and warnings") - parser.add_option('--count', action='store_true', - help="print total number of errors and warnings " - "to standard error and set exit code to 1 if " - "total is not null") - parser.add_option('--max-line-length', type='int', metavar='n', - default=MAX_LINE_LENGTH, - help="set maximum allowed line length " - "(default: %default)") - parser.add_option('--format', metavar='format', default='default', - help="set the error format [default|pylint|]") - parser.add_option('--diff', action='store_true', - help="report only lines changed according to the " - "unified diff received on STDIN") - group = parser.add_option_group("Testing Options") - group.add_option('--testsuite', metavar='dir', - help="run regression tests from dir") - group.add_option('--doctest', action='store_true', - help="run doctest on myself") - group.add_option('--benchmark', action='store_true', - help="measure processing speed") - group = parser.add_option_group("Configuration", description=( - "The project options are read from the [pep8] section of the .pep8 " - "file located in any parent folder of the path(s) being processed. " - "Allowed options are: %s." % ', '.join(parser.config_options))) - group.add_option('--config', metavar='path', default=config_file, - help="config file location (default: %default)") - - options, args = parser.parse_args(arglist) - options.reporter = None - - if options.testsuite: - args.append(options.testsuite) - elif not options.doctest: - if parse_argv and not args: - if os.path.exists('.pep8') or options.diff: - args = ['.'] - else: - parser.error('input not specified') - options = read_config(options, args, arglist, parser) - options.reporter = parse_argv and options.quiet == 1 and FileReport - - if options.filename: - options.filename = options.filename.split(',') - options.exclude = options.exclude.split(',') - if options.select: - options.select = options.select.split(',') - if options.ignore: - options.ignore = options.ignore.split(',') - elif not (options.select or - options.testsuite or options.doctest) and DEFAULT_IGNORE: - # The default choice: ignore controversial checks - # (for doctest and testsuite, all checks are required) - options.ignore = DEFAULT_IGNORE.split(',') - - if options.diff: - options.reporter = DiffReport - stdin = stdin_get_value() - options.selected_lines = parse_udiff(stdin, options.filename, args[0]) - args = sorted(options.selected_lines) - - return options, args - - -def _main(): - """Parse options and run checks on Python source.""" - pep8style = StyleGuide(parse_argv=True, config_file=True) - options = pep8style.options - if options.doctest: - import doctest - fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose) - fail_s, done_s = selftest(options) - count_failed = fail_s + fail_d - if not options.quiet: - count_passed = done_d + done_s - count_failed - print("%d passed and %d failed." % (count_passed, count_failed)) - print("Test failed." if count_failed else "Test passed.") - if count_failed: - sys.exit(1) - if options.testsuite: - init_tests(pep8style) - report = pep8style.check_files() - if options.statistics: - report.print_statistics() - if options.benchmark: - report.print_benchmark() - if options.testsuite and not options.quiet: - report.print_results() - if report.total_errors: - if options.count: - sys.stderr.write(str(report.total_errors) + '\n') - sys.exit(1) - - -if __name__ == '__main__': - _main()