diff options
| author | Ian Cordasco <graffatcolmingov@gmail.com> | 2016-06-25 10:12:13 -0500 |
|---|---|---|
| committer | Ian Cordasco <graffatcolmingov@gmail.com> | 2016-06-25 10:12:13 -0500 |
| commit | 1a2c68f5da8ae95b8a156ef6f6a772bf82cf0f88 (patch) | |
| tree | e125328f45274330a116d0ae659e20ad4c8367cf /src/flake8/main | |
| parent | 5c8d767626a31560494996cd02ec5d654734aab2 (diff) | |
| download | flake8-1a2c68f5da8ae95b8a156ef6f6a772bf82cf0f88.tar.gz | |
Move flake8 into src
This is an emerging best practice and there is little reason to not
follow it
Diffstat (limited to 'src/flake8/main')
| -rw-r--r-- | src/flake8/main/__init__.py | 1 | ||||
| -rw-r--r-- | src/flake8/main/application.py | 296 | ||||
| -rw-r--r-- | src/flake8/main/cli.py | 17 | ||||
| -rw-r--r-- | src/flake8/main/git.py | 207 | ||||
| -rw-r--r-- | src/flake8/main/mercurial.py | 128 | ||||
| -rw-r--r-- | src/flake8/main/options.py | 201 | ||||
| -rw-r--r-- | src/flake8/main/setuptools_command.py | 77 | ||||
| -rw-r--r-- | src/flake8/main/vcs.py | 39 |
8 files changed, 966 insertions, 0 deletions
diff --git a/src/flake8/main/__init__.py b/src/flake8/main/__init__.py new file mode 100644 index 0000000..d3aa1de --- /dev/null +++ b/src/flake8/main/__init__.py @@ -0,0 +1 @@ +"""Module containing the logic for the Flake8 entry-points.""" diff --git a/src/flake8/main/application.py b/src/flake8/main/application.py new file mode 100644 index 0000000..225c701 --- /dev/null +++ b/src/flake8/main/application.py @@ -0,0 +1,296 @@ +"""Module containing the application logic for Flake8.""" +from __future__ import print_function + +import logging +import sys +import time + +import flake8 +from flake8 import checker +from flake8 import defaults +from flake8 import style_guide +from flake8 import utils +from flake8.main import options +from flake8.options import aggregator +from flake8.options import manager +from flake8.plugins import manager as plugin_manager + +LOG = logging.getLogger(__name__) + + +class Application(object): + """Abstract our application into a class.""" + + def __init__(self, program='flake8', version=flake8.__version__): + # type: (str, str) -> NoneType + """Initialize our application. + + :param str program: + The name of the program/application that we're executing. + :param str version: + The version of the program/application we're executing. + """ + #: The timestamp when the Application instance was instantiated. + self.start_time = time.time() + #: The timestamp when the Application finished reported errors. + self.end_time = None + #: The name of the program being run + self.program = program + #: The version of the program being run + self.version = version + #: The instance of :class:`flake8.options.manager.OptionManager` used + #: to parse and handle the options and arguments passed by the user + self.option_manager = manager.OptionManager( + prog='flake8', version=flake8.__version__ + ) + options.register_default_options(self.option_manager) + + # We haven't found or registered our plugins yet, so let's defer + # printing the version until we aggregate options from config files + # and the command-line. First, let's clone our arguments on the CLI, + # then we'll attempt to remove ``--version`` so that we can avoid + # triggering the "version" action in optparse. If it's not there, we + # do not need to worry and we can continue. If it is, we successfully + # defer printing the version until just a little bit later. + # Similarly we have to defer printing the help text until later. + args = sys.argv[:] + try: + args.remove('--version') + except ValueError: + pass + try: + args.remove('--help') + except ValueError: + pass + try: + args.remove('-h') + except ValueError: + pass + + preliminary_opts, _ = self.option_manager.parse_args(args) + # Set the verbosity of the program + flake8.configure_logging(preliminary_opts.verbose, + preliminary_opts.output_file) + + #: The instance of :class:`flake8.plugins.manager.Checkers` + self.check_plugins = None + #: The instance of :class:`flake8.plugins.manager.Listeners` + self.listening_plugins = None + #: The instance of :class:`flake8.plugins.manager.ReportFormatters` + self.formatting_plugins = None + #: The user-selected formatter from :attr:`formatting_plugins` + self.formatter = None + #: The :class:`flake8.plugins.notifier.Notifier` for listening plugins + self.listener_trie = None + #: The :class:`flake8.style_guide.StyleGuide` built from the user's + #: options + self.guide = None + #: The :class:`flake8.checker.Manager` that will handle running all of + #: the checks selected by the user. + self.file_checker_manager = None + + #: The user-supplied options parsed into an instance of + #: :class:`optparse.Values` + self.options = None + #: The left over arguments that were not parsed by + #: :attr:`option_manager` + self.args = None + #: The number of errors, warnings, and other messages after running + #: flake8 and taking into account ignored errors and lines. + self.result_count = 0 + #: The total number of errors before accounting for ignored errors and + #: lines. + self.total_result_count = 0 + + #: Whether the program is processing a diff or not + self.running_against_diff = False + #: The parsed diff information + self.parsed_diff = {} + + def exit(self): + # type: () -> NoneType + """Handle finalization and exiting the program. + + This should be the last thing called on the application instance. It + will check certain options and exit appropriately. + """ + if self.options.count: + print(self.result_count) + + if not self.options.exit_zero: + raise SystemExit(self.result_count > 0) + + def find_plugins(self): + # type: () -> NoneType + """Find and load the plugins for this application. + + If :attr:`check_plugins`, :attr:`listening_plugins`, or + :attr:`formatting_plugins` are ``None`` then this method will update + them with the appropriate plugin manager instance. Given the expense + of finding plugins (via :mod:`pkg_resources`) we want this to be + idempotent and so only update those attributes if they are ``None``. + """ + if self.check_plugins is None: + self.check_plugins = plugin_manager.Checkers() + + if self.listening_plugins is None: + self.listening_plugins = plugin_manager.Listeners() + + if self.formatting_plugins is None: + self.formatting_plugins = plugin_manager.ReportFormatters() + + self.check_plugins.load_plugins() + self.listening_plugins.load_plugins() + self.formatting_plugins.load_plugins() + + def register_plugin_options(self): + # type: () -> NoneType + """Register options provided by plugins to our option manager.""" + self.check_plugins.register_options(self.option_manager) + self.check_plugins.register_plugin_versions(self.option_manager) + self.listening_plugins.register_options(self.option_manager) + self.formatting_plugins.register_options(self.option_manager) + + def parse_configuration_and_cli(self, argv=None): + # type: (Union[NoneType, List[str]]) -> NoneType + """Parse configuration files and the CLI options. + + :param list argv: + Command-line arguments passed in directly. + """ + if self.options is None and self.args is None: + self.options, self.args = aggregator.aggregate_options( + self.option_manager, argv + ) + + self.running_against_diff = self.options.diff + if self.running_against_diff: + self.parsed_diff = utils.parse_unified_diff() + + self.check_plugins.provide_options(self.option_manager, self.options, + self.args) + self.listening_plugins.provide_options(self.option_manager, + self.options, + self.args) + self.formatting_plugins.provide_options(self.option_manager, + self.options, + self.args) + + def make_formatter(self): + # type: () -> NoneType + """Initialize a formatter based on the parsed options.""" + if self.formatter is None: + self.formatter = self.formatting_plugins.get( + self.options.format, self.formatting_plugins['default'] + ).execute(self.options) + + def make_notifier(self): + # type: () -> NoneType + """Initialize our listener Notifier.""" + if self.listener_trie is None: + self.listener_trie = self.listening_plugins.build_notifier() + + def make_guide(self): + # type: () -> NoneType + """Initialize our StyleGuide.""" + if self.guide is None: + self.guide = style_guide.StyleGuide( + self.options, self.listener_trie, self.formatter + ) + + if self.running_against_diff: + self.guide.add_diff_ranges(self.parsed_diff) + + def make_file_checker_manager(self): + # type: () -> NoneType + """Initialize our FileChecker Manager.""" + if self.file_checker_manager is None: + self.file_checker_manager = checker.Manager( + style_guide=self.guide, + arguments=self.args, + checker_plugins=self.check_plugins, + ) + + def run_checks(self): + # type: () -> NoneType + """Run the actual checks with the FileChecker Manager. + + This method encapsulates the logic to make a + :class:`~flake8.checker.Manger` instance run the checks it is + managing. + """ + files = None + if self.running_against_diff: + files = list(sorted(self.parsed_diff.keys())) + self.file_checker_manager.start(files) + self.file_checker_manager.run() + LOG.info('Finished running') + self.file_checker_manager.stop() + self.end_time = time.time() + + def report_benchmarks(self): + """Aggregate, calculate, and report benchmarks for this run.""" + if not self.options.benchmark: + return + + time_elapsed = self.end_time - self.start_time + statistics = [('seconds elapsed', time_elapsed)] + add_statistic = statistics.append + for statistic in (defaults.STATISTIC_NAMES + ('files',)): + value = self.file_checker_manager.statistics[statistic] + total_description = 'total ' + statistic + ' processed' + add_statistic((total_description, value)) + per_second_description = statistic + ' processed per second' + add_statistic((per_second_description, int(value / time_elapsed))) + + self.formatter.show_benchmarks(statistics) + + def report_errors(self): + # type: () -> NoneType + """Report all the errors found by flake8 3.0. + + This also updates the :attr:`result_count` attribute with the total + number of errors, warnings, and other messages found. + """ + LOG.info('Reporting errors') + results = self.file_checker_manager.report() + self.total_result_count, self.result_count = results + LOG.info('Found a total of %d results and reported %d', + self.total_result_count, self.result_count) + + def initialize(self, argv): + # type: () -> NoneType + """Initialize the application to be run. + + This finds the plugins, registers their options, and parses the + command-line arguments. + """ + self.find_plugins() + self.register_plugin_options() + self.parse_configuration_and_cli(argv) + self.make_formatter() + self.make_notifier() + self.make_guide() + self.make_file_checker_manager() + + def _run(self, argv): + # type: (Union[NoneType, List[str]]) -> NoneType + self.initialize(argv) + self.run_checks() + self.report_errors() + self.report_benchmarks() + + def run(self, argv=None): + # type: (Union[NoneType, List[str]]) -> NoneType + """Run our application. + + This method will also handle KeyboardInterrupt exceptions for the + entirety of the flake8 application. If it sees a KeyboardInterrupt it + will forcibly clean up the :class:`~flake8.checker.Manager`. + """ + try: + self._run(argv) + except KeyboardInterrupt as exc: + LOG.critical('Caught keyboard interrupt from user') + LOG.exception(exc) + self.file_checker_manager._force_cleanup() diff --git a/src/flake8/main/cli.py b/src/flake8/main/cli.py new file mode 100644 index 0000000..29bd159 --- /dev/null +++ b/src/flake8/main/cli.py @@ -0,0 +1,17 @@ +"""Command-line implementation of flake8.""" +from flake8.main import application + + +def main(argv=None): + # type: (Union[NoneType, List[str]]) -> NoneType + """Main entry-point for the flake8 command-line tool. + + This handles the creation of an instance of :class:`Application`, runs it, + and then exits the application. + + :param list argv: + The arguments to be passed to the application for parsing. + """ + app = application.Application() + app.run(argv) + app.exit() diff --git a/src/flake8/main/git.py b/src/flake8/main/git.py new file mode 100644 index 0000000..bae0233 --- /dev/null +++ b/src/flake8/main/git.py @@ -0,0 +1,207 @@ +"""Module containing the main git hook interface and helpers. + +.. autofunction:: hook +.. autofunction:: install + +""" +import contextlib +import os +import shutil +import stat +import subprocess +import sys +import tempfile + +from flake8 import defaults +from flake8 import exceptions + +__all__ = ('hook', 'install') + + +def hook(lazy=False, strict=False): + """Execute Flake8 on the files in git's index. + + Determine which files are about to be committed and run Flake8 over them + to check for violations. + + :param bool lazy: + Find files not added to the index prior to committing. This is useful + if you frequently use ``git commit -a`` for example. This defaults to + False since it will otherwise include files not in the index. + :param bool strict: + If True, return the total number of errors/violations found by Flake8. + This will cause the hook to fail. + :returns: + Total number of errors found during the run. + :rtype: + int + """ + # NOTE(sigmavirus24): Delay import of application until we need it. + from flake8.main import application + app = application.Application() + with make_temporary_directory() as tempdir: + filepaths = list(copy_indexed_files_to(tempdir, lazy)) + app.initialize(filepaths) + app.run_checks() + + app.report_errors() + if strict: + return app.result_count + return 0 + + +def install(): + """Install the git hook script. + + This searches for the ``.git`` directory and will install an executable + pre-commit python script in the hooks sub-directory if one does not + already exist. + + :returns: + True if successful, False if the git directory doesn't exist. + :rtype: + bool + :raises: + flake8.exceptions.GitHookAlreadyExists + """ + git_directory = find_git_directory() + if git_directory is None or not os.path.exists(git_directory): + return False + + hooks_directory = os.path.join(git_directory, 'hooks') + if not os.path.exists(hooks_directory): + os.mkdir(hooks_directory) + + pre_commit_file = os.path.abspath( + os.path.join(hooks_directory, 'pre-commit') + ) + if os.path.exists(pre_commit_file): + raise exceptions.GitHookAlreadyExists( + 'File already exists', + path=pre_commit_file, + ) + + executable = get_executable() + + with open(pre_commit_file, 'w') as fd: + fd.write(_HOOK_TEMPLATE.format(executable=executable)) + + # NOTE(sigmavirus24): The following sets: + # - read, write, and execute permissions for the owner + # - read permissions for people in the group + # - read permissions for other people + # The owner needs the file to be readable, writable, and executable + # so that git can actually execute it as a hook. + pre_commit_permissions = stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH + os.chmod(pre_commit_file, pre_commit_permissions) + return True + + +def get_executable(): + if sys.executable is not None: + return sys.executable + return '/usr/bin/env python' + + +def find_git_directory(): + rev_parse = piped_process(['git', 'rev-parse', '--git-dir']) + + (stdout, _) = rev_parse.communicate() + stdout = to_text(stdout) + + if rev_parse.returncode == 0: + return stdout.strip() + return None + + +def copy_indexed_files_to(temporary_directory, lazy): + modified_files = find_modified_files(lazy) + for filename in modified_files: + contents = get_staged_contents_from(filename) + yield copy_file_to(temporary_directory, filename, contents) + + +def copy_file_to(destination_directory, filepath, contents): + directory, filename = os.path.split(os.path.abspath(filepath)) + temporary_directory = make_temporary_directory_from(destination_directory, + directory) + if not os.path.exists(temporary_directory): + os.makedirs(temporary_directory) + temporary_filepath = os.path.join(temporary_directory, filename) + with open(temporary_filepath, 'wb') as fd: + fd.write(contents) + return temporary_filepath + + +def make_temporary_directory_from(destination, directory): + prefix = os.path.commonprefix([directory, destination]) + common_directory_path = os.path.relpath(directory, start=prefix) + return os.path.join(destination, common_directory_path) + + +def find_modified_files(lazy): + diff_index = piped_process( + ['git', 'diff-index', '--cached', '--name-only', + '--diff-filter=ACMRTUXB', 'HEAD'], + ) + + (stdout, _) = diff_index.communicate() + stdout = to_text(stdout) + return stdout.splitlines() + + +def get_staged_contents_from(filename): + git_show = piped_process(['git', 'show', ':{0}'.format(filename)]) + (stdout, _) = git_show.communicate() + return stdout + + +@contextlib.contextmanager +def make_temporary_directory(): + temporary_directory = tempfile.mkdtemp() + yield temporary_directory + shutil.rmtree(temporary_directory, ignore_errors=True) + + +def to_text(string): + """Ensure that the string is text.""" + if callable(getattr(string, 'decode', None)): + return string.decode('utf-8') + return string + + +def piped_process(command): + return subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + +def git_config_for(parameter): + config = piped_process(['git', 'config', '--get', '--bool', parameter]) + (stdout, _) = config.communicate() + return to_text(stdout) + + +def config_for(parameter): + environment_variable = 'flake8_{0}'.format(parameter).upper() + git_variable = 'flake8.{0}'.format(parameter) + value = os.environ.get(environment_variable, git_config_for(git_variable)) + return value.lower() in defaults.TRUTHY_VALUES + + +_HOOK_TEMPLATE = """#!{executable} +import os +import sys + +from flake8.main import git + +if __name__ == '__main__': + sys.exit( + git.hook( + strict=git.config_for('strict'), + lazy=git.config_for('lazy'), + ) + ) +""" diff --git a/src/flake8/main/mercurial.py b/src/flake8/main/mercurial.py new file mode 100644 index 0000000..d067612 --- /dev/null +++ b/src/flake8/main/mercurial.py @@ -0,0 +1,128 @@ +"""Module containing the main mecurial hook interface and helpers. + +.. autofunction:: hook +.. autofunction:: install + +""" +import configparser +import os +import subprocess + +from flake8 import exceptions as exc + +__all__ = ('hook', 'install') + + +def hook(ui, repo, **kwargs): + """Execute Flake8 on the repository provided by Mercurial. + + To understand the parameters read more of the Mercurial documentation + around Hooks: https://www.mercurial-scm.org/wiki/Hook. + + We avoid using the ``ui`` attribute because it can cause issues with + the GPL license tha Mercurial is under. We don't import it, but we + avoid using it all the same. + """ + from flake8.main import application + hgrc = find_hgrc(create_if_missing=False) + if hgrc is None: + print('Cannot locate your root mercurial repository.') + raise SystemExit(True) + + hgconfig = configparser_for(hgrc) + strict = hgconfig.get('flake8', 'strict', fallback=True) + + filenames = list(get_filenames_from(repo, kwargs)) + + app = application.Application() + app.run(filenames) + + if strict: + return app.result_count + return 0 + + +def install(): + """Ensure that the mercurial hooks are installed.""" + hgrc = find_hgrc(create_if_missing=True) + if hgrc is None: + return False + + hgconfig = configparser_for(hgrc) + + if not hgconfig.has_section('hooks'): + hgconfig.add_section('hooks') + + if hgconfig.has_option('hooks', 'commit'): + raise exc.MercurialCommitHookAlreadyExists( + path=hgrc, + value=hgconfig.get('hooks', 'commit'), + ) + + if hgconfig.has_option('hooks', 'qrefresh'): + raise exc.MercurialQRefreshHookAlreadyExists( + path=hgrc, + value=hgconfig.get('hooks', 'qrefresh'), + ) + + hgconfig.set('hooks', 'commit', 'python:flake8.main.mercurial.hook') + hgconfig.set('hooks', 'qrefresh', 'python:flake8.main.mercurial.hook') + + if not hgconfig.has_section('flake8'): + hgconfig.add_section('flake8') + + if not hgconfig.has_option('flake8', 'strict'): + hgconfig.set('flake8', 'strict', False) + + with open(hgrc, 'w') as fd: + hgconfig.write(fd) + + return True + + +def get_filenames_from(repository, kwargs): + seen_filenames = set() + node = kwargs['node'] + for revision in range(repository[node], len(repository)): + for filename in repository[revision].files(): + full_filename = os.path.join(repository.root, filename) + have_seen_filename = full_filename in seen_filenames + filename_does_not_exist = not os.path.exists(full_filename) + if have_seen_filename or filename_does_not_exist: + continue + + seen_filenames.add(full_filename) + if full_filename.endswith('.py'): + yield full_filename + + +def find_hgrc(create_if_missing=False): + root = subprocess.Popen( + ['hg', 'root'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + (hg_directory, _) = root.communicate() + if callable(getattr(hg_directory, 'decode', None)): + hg_directory = hg_directory.decode('utf-8') + + if not os.path.isdir(hg_directory): + return None + + hgrc = os.path.abspath( + os.path.join(hg_directory, '.hg', 'hgrc') + ) + if not os.path.exists(hgrc): + if create_if_missing: + open(hgrc, 'w').close() + else: + return None + + return hgrc + + +def configparser_for(path): + parser = configparser.ConfigParser(interpolation=None) + parser.read(path) + return parser diff --git a/src/flake8/main/options.py b/src/flake8/main/options.py new file mode 100644 index 0000000..c725c38 --- /dev/null +++ b/src/flake8/main/options.py @@ -0,0 +1,201 @@ +"""Contains the logic for all of the default options for Flake8.""" +from flake8 import defaults +from flake8.main import vcs + + +def register_default_options(option_manager): + """Register the default options on our OptionManager. + + The default options include: + + - ``-v``/``--verbose`` + - ``-q``/``--quiet`` + - ``--count`` + - ``--diff`` + - ``--exclude`` + - ``--filename`` + - ``--format`` + - ``--hang-closing`` + - ``--ignore`` + - ``--max-line-length`` + - ``--select`` + - ``--disable-noqa`` + - ``--show-source`` + - ``--statistics`` + - ``--enable-extensions`` + - ``--exit-zero`` + - ``-j``/``--jobs`` + - ``--output-file`` + - ``--append-config`` + - ``--config`` + - ``--isolated`` + """ + add_option = option_manager.add_option + + # pep8 options + add_option( + '-v', '--verbose', default=0, action='count', + parse_from_config=True, + help='Print more information about what is happening in flake8.' + ' This option is repeatable and will increase verbosity each ' + 'time it is repeated.', + ) + add_option( + '-q', '--quiet', default=0, action='count', + parse_from_config=True, + help='Report only file names, or nothing. This option is repeatable.', + ) + + add_option( + '--count', action='store_true', parse_from_config=True, + help='Print total number of errors and warnings to standard error and' + ' set the exit code to 1 if total is not empty.', + ) + + add_option( + '--diff', action='store_true', + help='Report changes only within line number ranges in the unified ' + 'diff provided on standard in by the user.', + ) + + add_option( + '--exclude', metavar='patterns', default=defaults.EXCLUDE, + comma_separated_list=True, parse_from_config=True, + normalize_paths=True, + help='Comma-separated list of files or directories to exclude.' + ' (Default: %default)', + ) + + add_option( + '--filename', metavar='patterns', default='*.py', + parse_from_config=True, comma_separated_list=True, + help='Only check for filenames matching the patterns in this comma-' + 'separated list. (Default: %default)', + ) + + add_option( + '--stdin-display-name', default='stdin', + help='The name used when reporting errors from code passed via stdin.' + ' This is useful for editors piping the file contents to flake8.' + ' (Default: %default)', + ) + + # TODO(sigmavirus24): Figure out --first/--repeat + + # NOTE(sigmavirus24): We can't use choices for this option since users can + # freely provide a format string and that will break if we restrict their + # choices. + add_option( + '--format', metavar='format', default='default', + parse_from_config=True, + help='Format errors according to the chosen formatter.', + ) + + add_option( + '--hang-closing', action='store_true', parse_from_config=True, + help='Hang closing bracket instead of matching indentation of opening' + " bracket's line.", + ) + + add_option( + '--ignore', metavar='errors', default=defaults.IGNORE, + parse_from_config=True, comma_separated_list=True, + help='Comma-separated list of errors and warnings to ignore (or skip).' + ' For example, ``--ignore=E4,E51,W234``. (Default: %default)', + ) + + add_option( + '--max-line-length', type='int', metavar='n', + default=defaults.MAX_LINE_LENGTH, parse_from_config=True, + help='Maximum allowed line length for the entirety of this run. ' + '(Default: %default)', + ) + + add_option( + '--select', metavar='errors', default=defaults.SELECT, + parse_from_config=True, comma_separated_list=True, + help='Comma-separated list of errors and warnings to enable.' + ' For example, ``--select=E4,E51,W234``. (Default: %default)', + ) + + add_option( + '--disable-noqa', default=False, parse_from_config=True, + action='store_true', + help='Disable the effect of "# noqa". This will report errors on ' + 'lines with "# noqa" at the end.' + ) + + # TODO(sigmavirus24): Decide what to do about --show-pep8 + + add_option( + '--show-source', action='store_true', parse_from_config=True, + help='Show the source generate each error or warning.', + ) + + add_option( + '--statistics', action='store_true', parse_from_config=True, + help='Count errors and warnings.', + ) + + # Flake8 options + add_option( + '--enable-extensions', default='', parse_from_config=True, + comma_separated_list=True, type='string', + help='Enable plugins and extensions that are otherwise disabled ' + 'by default', + ) + + add_option( + '--exit-zero', action='store_true', + help='Exit with status code "0" even if there are errors.', + ) + + add_option( + '--install-hook', action='callback', type='choice', + choices=vcs.choices(), callback=vcs.install, + help='Install a hook that is run prior to a commit for the supported ' + 'version control systema.' + ) + + add_option( + '-j', '--jobs', type='string', default='auto', parse_from_config=True, + help='Number of subprocesses to use to run checks in parallel. ' + 'This is ignored on Windows. The default, "auto", will ' + 'auto-detect the number of processors available to use.' + ' (Default: %default)', + ) + + add_option( + '--output-file', default=None, type='string', parse_from_config=True, + # callback=callbacks.redirect_stdout, + help='Redirect report to a file.', + ) + + # Config file options + + add_option( + '--append-config', action='append', + help='Provide extra config files to parse in addition to the files ' + 'found by Flake8 by default. These files are the last ones read ' + 'and so they take the highest precedence when multiple files ' + 'provide the same option.', + ) + + add_option( + '--config', default=None, + help='Path to the config file that will be the authoritative config ' + 'source. This will cause Flake8 to ignore all other ' + 'configuration files.' + ) + + add_option( + '--isolated', default=False, action='store_true', + help='Ignore all found configuration files.', + ) + + # Benchmarking + + add_option( + '--benchmark', default=False, action='store_true', + help='Print benchmark information about this run of Flake8', + ) diff --git a/src/flake8/main/setuptools_command.py b/src/flake8/main/setuptools_command.py new file mode 100644 index 0000000..1c27bf6 --- /dev/null +++ b/src/flake8/main/setuptools_command.py @@ -0,0 +1,77 @@ +"""The logic for Flake8's integration with setuptools.""" +import os + +import setuptools + +from flake8.main import application as app + + +class Flake8(setuptools.Command): + """Run Flake8 via setuptools/distutils for registered modules.""" + + description = 'Run Flake8 on modules registered in setup.py' + # NOTE(sigmavirus24): If we populated this with a list of tuples, users + # could do something like ``python setup.py flake8 --ignore=E123,E234`` + # but we would have to redefine it and we can't define it dynamically. + # Since I refuse to copy-and-paste the options here or maintain two lists + # of options, and since this will break when users use plugins that + # provide command-line options, we are leaving this empty. If users want + # to configure this command, they can do so through config files. + user_options = [] + + def initialize_options(self): + """Override this method to initialize our application.""" + pass + + def finalize_options(self): + """Override this to parse the parameters.""" + pass + + def package_files(self): + """Collect the files/dirs included in the registered modules.""" + seen_package_directories = () + directories = self.distribution.package_dir or {} + empty_directory_exists = '' in directories + packages = self.distribution.packages or [] + for package in packages: + package_directory = package + if package in directories: + package_directory = directories[package] + elif empty_directory_exists: + package_directory = os.path.join(directories[''], + package_directory) + + # NOTE(sigmavirus24): Do not collect submodules, e.g., + # if we have: + # - flake8/ + # - flake8/plugins/ + # Flake8 only needs ``flake8/`` to be provided. It will + # recurse on its own. + if package_directory.startswith(seen_package_directories): + continue + + seen_package_directories += (package_directory,) + yield package_directory + + def module_files(self): + """Collect the files listed as py_modules.""" + modules = self.distribution.py_modules or [] + filename_from = '{0}.py'.format + for module in modules: + yield filename_from(module) + + def distribution_files(self): + """Collect package and module files.""" + for package in self.package_files(): + yield package + + for module in self.module_files(): + yield module + + yield 'setup.py' + + def run(self): + """Run the Flake8 application.""" + flake8 = app.Application() + flake8.run(list(self.distribution_files())) + flake8.exit() diff --git a/src/flake8/main/vcs.py b/src/flake8/main/vcs.py new file mode 100644 index 0000000..6f7499e --- /dev/null +++ b/src/flake8/main/vcs.py @@ -0,0 +1,39 @@ +"""Module containing some of the logic for our VCS installation logic.""" +from flake8 import exceptions as exc +from flake8.main import git +from flake8.main import mercurial + + +# NOTE(sigmavirus24): In the future, we may allow for VCS hooks to be defined +# as plugins, e.g., adding a flake8.vcs entry-point. In that case, this +# dictionary should disappear, and this module might contain more code for +# managing those bits (in conjuntion with flake8.plugins.manager). +_INSTALLERS = { + 'git': git.install, + 'mercurial': mercurial.install, +} + + +def install(option, option_string, value, parser): + """Determine which version control hook to install. + + For more information about the callback signature, see: + https://docs.python.org/2/library/optparse.html#optparse-option-callbacks + """ + installer = _INSTALLERS.get(value) + errored = False + successful = False + try: + successful = installer() + except exc.HookInstallationError as hook_error: + print(str(hook_error)) + errored = True + + if not successful: + print('Could not find the {0} directory'.format(value)) + raise SystemExit(not successful and errored) + + +def choices(): + """Return the list of VCS choices.""" + return list(_INSTALLERS.keys()) |
