1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3
4"""Reporter foundation for coverage.py."""
5
6import os
7
8from coverage.files import prep_patterns, FnmatchMatcher
9from coverage.misc import CoverageException, NoSource, NotPython, isolate_module
10
11os = isolate_module(os)
12
13
14class Reporter(object):
15    """A base class for all reporters."""
16
17    def __init__(self, coverage, config):
18        """Create a reporter.
19
20        `coverage` is the coverage instance. `config` is an instance  of
21        CoverageConfig, for controlling all sorts of behavior.
22
23        """
24        self.coverage = coverage
25        self.config = config
26
27        # The FileReporters to report on.  Set by find_file_reporters.
28        self.file_reporters = []
29
30        # The directory into which to place the report, used by some derived
31        # classes.
32        self.directory = None
33
34    def find_file_reporters(self, morfs):
35        """Find the FileReporters we'll report on.
36
37        `morfs` is a list of modules or file names.
38
39        """
40        reporters = self.coverage._get_file_reporters(morfs)
41
42        if self.config.include:
43            matcher = FnmatchMatcher(prep_patterns(self.config.include))
44            reporters = [fr for fr in reporters if matcher.match(fr.filename)]
45
46        if self.config.omit:
47            matcher = FnmatchMatcher(prep_patterns(self.config.omit))
48            reporters = [fr for fr in reporters if not matcher.match(fr.filename)]
49
50        self.file_reporters = sorted(reporters)
51
52    def report_files(self, report_fn, morfs, directory=None):
53        """Run a reporting function on a number of morfs.
54
55        `report_fn` is called for each relative morf in `morfs`.  It is called
56        as::
57
58            report_fn(file_reporter, analysis)
59
60        where `file_reporter` is the `FileReporter` for the morf, and
61        `analysis` is the `Analysis` for the morf.
62
63        """
64        self.find_file_reporters(morfs)
65
66        if not self.file_reporters:
67            raise CoverageException("No data to report.")
68
69        self.directory = directory
70        if self.directory and not os.path.exists(self.directory):
71            os.makedirs(self.directory)
72
73        for fr in self.file_reporters:
74            try:
75                report_fn(fr, self.coverage._analyze(fr))
76            except NoSource:
77                if not self.config.ignore_errors:
78                    raise
79            except NotPython:
80                # Only report errors for .py files, and only if we didn't
81                # explicitly suppress those errors.
82                # NotPython is only raised by PythonFileReporter, which has a
83                # should_be_python() method.
84                if fr.should_be_python() and not self.config.ignore_errors:
85                    raise
86