diff --git a/prospector/config/__init__.py b/prospector/config/__init__.py index 2168767d..e43b6681 100644 --- a/prospector/config/__init__.py +++ b/prospector/config/__init__.py @@ -12,8 +12,16 @@ class ProspectorConfig(object): def __init__(self): self.config, self.arguments = self._configure_prospector() - self.path = self._get_work_path(self.config, self.arguments) - self.profile, self.profile_names, self.strictness = self._get_profile(self.path, self.config) + + self.paths = self._get_work_path(self.config, self.arguments) + self.explicit_file_mode = all(map(os.path.isfile, self.paths)) + + if os.path.isdir(self.paths[0]): + self.workdir = self.paths[0] + else: + self.workdir = os.getcwd() + + self.profile, self.profile_names, self.strictness = self._get_profile(self.workdir, self.config) self.libraries = self._find_used_libraries(self.config) self.tools_to_run = self._determine_tool_runners(self.config, self.profile) self.ignores = self._determine_ignores(self.config, self.profile, self.libraries) @@ -53,15 +61,7 @@ def _get_work_path(self, config, arguments): paths = arguments['checkpath'] else: paths = [os.getcwd()] - # TODO: Currently prospector can only handle one path at a time. This is - # because the automatic loading of configuration files becomes complicated - # to explain when multiple paths are to be checked. Should each path allow - # its own configuration? Or should one single global configuration be used? - # If so, where from? - # See the discussion: - # https://github.com/landscapeio/prospector/issues/61 - # https://github.com/landscapeio/prospector/pull/68 - return paths[0] + return paths def _get_profile(self, path, config): # Use other specialty profiles based on options @@ -137,7 +137,7 @@ def _find_used_libraries(self, config): # Bring in adaptors that we automatically detect are needed if config.autodetect: - for name, adaptor in autodetect_libraries(self.path): + for name, adaptor in autodetect_libraries(self.workdir): libraries.append(name) # Bring in adaptors for the specified libraries diff --git a/prospector/config/configuration.py b/prospector/config/configuration.py index 50824ef9..f228e178 100644 --- a/prospector/config/configuration.py +++ b/prospector/config/configuration.py @@ -272,7 +272,8 @@ def build_command_line_source(prog=None, description='Performs static analysis o positional = ( ('checkpath', { 'help': 'The path to a Python project to inspect. Defaults to PWD' - ' if not specified.', + ' if not specified. If multiple paths are specified,' + ' they must all be files (no directories).', 'metavar': 'PATH', 'nargs': '*', }), diff --git a/prospector/finder.py b/prospector/finder.py index aae5c86d..16ed8fc8 100644 --- a/prospector/finder.py +++ b/prospector/finder.py @@ -1,22 +1,22 @@ import os -class SingleFile(object): +class SingleFiles(object): """ When prospector is run in 'single file mode' - that is, the argument is a python module rather than a directory - then we'll use this object instead of the FoundFiles to give all the functionality needed to check a single file. """ - def __init__(self, filepath): - self.filepath = filepath - self.rootpath = os.getcwd() + def __init__(self, files, rootpath): + self.files = files + self.rootpath = rootpath def _check(self, checkpath, abspath=True): if abspath: checkpath = os.path.abspath(checkpath) - return checkpath == os.path.abspath(self.filepath) - return checkpath == self.filepath + return checkpath in map(os.path.abspath, self.files) + return checkpath in self.files def check_module(self, filepath, abspath=True, even_if_ignored=False): return self._check(filepath, abspath) @@ -28,25 +28,30 @@ def check_file(self, filepath, abspath=True, even_if_ignored=False): return self._check(filepath, abspath) def iter_file_paths(self, abspath=True, include_ignored=False): - yield os.path.abspath(self.filepath) if abspath else self.filepath + for filepath in self.files: + yield os.path.abspath(filepath) if abspath else filepath def iter_package_paths(self, abspath=True, include_ignored=False): - yield os.path.abspath(self.filepath) if abspath else self.filepath + for filepath in self.files: + yield os.path.abspath(filepath) if abspath else filepath def iter_directory_paths(self, abspath=True, include_ignored=False): - yield os.path.abspath(self.filepath) if abspath else self.filepath + for filepath in self.files: + filepath = os.path.dirname(filepath) + yield os.path.abspath(filepath) if abspath else filepath def iter_module_paths(self, abspath=True, include_ignored=False): - yield os.path.abspath(self.filepath) if abspath else self.filepath + for filepath in self.files: + yield os.path.abspath(filepath) if abspath else filepath def to_absolute_path(self, path): return os.path.abspath(os.path.join(self.rootpath, path)) def get_minimal_syspath(self, absolute_paths=True): - path = os.path.dirname(self.filepath) + paths = list(set(map(os.path.dirname, self.files))) if absolute_paths: - path = os.path.abspath(path) - return [path] + paths = map(os.path.abspath, paths) + return [self.rootpath] + paths class FoundFiles(object): @@ -199,15 +204,16 @@ def _find_paths(ignore, curpath, rootpath): return files, modules, packages, directories -def find_python(ignores, path_argument): +def find_python(ignores, paths, explicit_file_mode, workdir=None): """ Returns a FoundFiles class containing a list of files, packages, directories, where files are simply all python (.py) files, packages are directories containing an `__init__.py` file, and directories is a list of all directories. All paths are relative to the dirpath argument. """ - if os.path.isdir(path_argument): - files, modules, directories, packages = _find_paths(ignores, path_argument, path_argument) - return FoundFiles(path_argument, files, modules, directories, packages, ignores) + if explicit_file_mode: + return SingleFiles(paths, workdir or os.getcwd()) else: - return SingleFile(path_argument) + assert len(paths) == 1 + files, modules, directories, packages = _find_paths(ignores, paths[0], paths[0]) + return FoundFiles(paths[0], files, modules, directories, packages, ignores) diff --git a/prospector/formatters/text.py b/prospector/formatters/text.py index 65e0c32e..cf5d780d 100644 --- a/prospector/formatters/text.py +++ b/prospector/formatters/text.py @@ -11,6 +11,7 @@ class TextFormatter(Formatter): summary_labels = ( + ('path', 'Path'), ('started', 'Started'), ('completed', 'Finished'), ('time_taken', 'Time Taken', lambda x: '%s seconds' % x), diff --git a/prospector/run.py b/prospector/run.py index ffe7dc08..15b22938 100644 --- a/prospector/run.py +++ b/prospector/run.py @@ -20,21 +20,15 @@ class Prospector(object): def __init__(self, config): self.config = config - - if os.path.isdir(config.path): - self.rootpath = config.path - else: - self.rootpath = os.getcwd() - self.summary = None self.messages = None def process_messages(self, messages): for message in messages: if self.config.absolute_paths: - message.to_absolute_path(self.rootpath) + message.to_absolute_path(self.config.workdir) else: - message.to_relative_path(self.rootpath) + message.to_relative_path(self.config.workdir) if self.config.blending: messages = blender.blend(messages) @@ -47,7 +41,8 @@ def execute(self): } summary.update(self.config.get_summary_information()) - found_files = find_python(self.config.ignores, self.config.path) + found_files = find_python(self.config.ignores, self.config.paths, + self.config.explicit_file_mode, self.config.workdir) # Run the tools messages = [] @@ -65,7 +60,7 @@ def execute(self): else: toolname = 'Unknown' - loc = Location(self.config.path, None, None, None, None) + loc = Location(self.config.workdir, None, None, None, None) msg = 'Tool %s failed to run (exception was raised)' % ( toolname, ) @@ -105,10 +100,12 @@ def print_messages(self, write_to=None): self.summary['formatter'] = output_format formatter = FORMATTERS[output_format](self.summary, self.messages) + print_messages = not self.config.summary_only and self.messages + # Produce the output write_to.write(formatter.render( summary=not self.config.messages_only, - messages=not self.config.summary_only, + messages=print_messages, )) write_to.write('\n') @@ -127,6 +124,13 @@ def main(): # Get our configuration config = ProspectorConfig() + paths = config.paths + if len(paths) > 1 and not all([os.path.isfile(path) for path in paths]): + sys.stderr.write('\nIn multi-path mode, all inputs must be files, ' + 'not directories.\n\n') + get_parser().print_usage() + sys.exit(2) + # Make it so prospector = Prospector(config) prospector.execute() diff --git a/prospector/tools/pylint/__init__.py b/prospector/tools/pylint/__init__.py index b78b68d0..0e20893c 100644 --- a/prospector/tools/pylint/__init__.py +++ b/prospector/tools/pylint/__init__.py @@ -166,7 +166,7 @@ def configure(self, prospector_config, found_files): if pylintrc is None: pylintrc = find_pylintrc() if pylintrc is None: - pylintrc_path = os.path.join(prospector_config.path, '.pylintrc') + pylintrc_path = os.path.join(prospector_config.workdir, '.pylintrc') if os.path.exists(pylintrc_path): pylintrc = pylintrc_path