• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""distutils.command.check
2
3Implements the Distutils 'check' command.
4"""
5from distutils.core import Command
6from distutils.errors import DistutilsSetupError
7
8try:
9    # docutils is installed
10    from docutils.utils import Reporter
11    from docutils.parsers.rst import Parser
12    from docutils import frontend
13    from docutils import nodes
14    from io import StringIO
15
16    class SilentReporter(Reporter):
17
18        def __init__(self, source, report_level, halt_level, stream=None,
19                     debug=0, encoding='ascii', error_handler='replace'):
20            self.messages = []
21            Reporter.__init__(self, source, report_level, halt_level, stream,
22                              debug, encoding, error_handler)
23
24        def system_message(self, level, message, *children, **kwargs):
25            self.messages.append((level, message, children, kwargs))
26            return nodes.system_message(message, level=level,
27                                        type=self.levels[level],
28                                        *children, **kwargs)
29
30    HAS_DOCUTILS = True
31except Exception:
32    # Catch all exceptions because exceptions besides ImportError probably
33    # indicate that docutils is not ported to Py3k.
34    HAS_DOCUTILS = False
35
36class check(Command):
37    """This command checks the meta-data of the package.
38    """
39    description = ("perform some checks on the package")
40    user_options = [('metadata', 'm', 'Verify meta-data'),
41                    ('restructuredtext', 'r',
42                     ('Checks if long string meta-data syntax '
43                      'are reStructuredText-compliant')),
44                    ('strict', 's',
45                     'Will exit with an error if a check fails')]
46
47    boolean_options = ['metadata', 'restructuredtext', 'strict']
48
49    def initialize_options(self):
50        """Sets default values for options."""
51        self.restructuredtext = 0
52        self.metadata = 1
53        self.strict = 0
54        self._warnings = 0
55
56    def finalize_options(self):
57        pass
58
59    def warn(self, msg):
60        """Counts the number of warnings that occurs."""
61        self._warnings += 1
62        return Command.warn(self, msg)
63
64    def run(self):
65        """Runs the command."""
66        # perform the various tests
67        if self.metadata:
68            self.check_metadata()
69        if self.restructuredtext:
70            if HAS_DOCUTILS:
71                self.check_restructuredtext()
72            elif self.strict:
73                raise DistutilsSetupError('The docutils package is needed.')
74
75        # let's raise an error in strict mode, if we have at least
76        # one warning
77        if self.strict and self._warnings > 0:
78            raise DistutilsSetupError('Please correct your package.')
79
80    def check_metadata(self):
81        """Ensures that all required elements of meta-data are supplied.
82
83        name, version, URL, (author and author_email) or
84        (maintainer and maintainer_email)).
85
86        Warns if any are missing.
87        """
88        metadata = self.distribution.metadata
89
90        missing = []
91        for attr in ('name', 'version', 'url'):
92            if not (hasattr(metadata, attr) and getattr(metadata, attr)):
93                missing.append(attr)
94
95        if missing:
96            self.warn("missing required meta-data: %s"  % ', '.join(missing))
97        if metadata.author:
98            if not metadata.author_email:
99                self.warn("missing meta-data: if 'author' supplied, " +
100                          "'author_email' must be supplied too")
101        elif metadata.maintainer:
102            if not metadata.maintainer_email:
103                self.warn("missing meta-data: if 'maintainer' supplied, " +
104                          "'maintainer_email' must be supplied too")
105        else:
106            self.warn("missing meta-data: either (author and author_email) " +
107                      "or (maintainer and maintainer_email) " +
108                      "must be supplied")
109
110    def check_restructuredtext(self):
111        """Checks if the long string fields are reST-compliant."""
112        data = self.distribution.get_long_description()
113        for warning in self._check_rst_data(data):
114            line = warning[-1].get('line')
115            if line is None:
116                warning = warning[1]
117            else:
118                warning = '%s (line %s)' % (warning[1], line)
119            self.warn(warning)
120
121    def _check_rst_data(self, data):
122        """Returns warnings when the provided data doesn't compile."""
123        # the include and csv_table directives need this to be a path
124        source_path = self.distribution.script_name or 'setup.py'
125        parser = Parser()
126        settings = frontend.OptionParser(components=(Parser,)).get_default_values()
127        settings.tab_width = 4
128        settings.pep_references = None
129        settings.rfc_references = None
130        reporter = SilentReporter(source_path,
131                          settings.report_level,
132                          settings.halt_level,
133                          stream=settings.warning_stream,
134                          debug=settings.debug,
135                          encoding=settings.error_encoding,
136                          error_handler=settings.error_encoding_error_handler)
137
138        document = nodes.document(settings, reporter, source=source_path)
139        document.note_source(source_path, -1)
140        try:
141            parser.parse(data, document)
142        except AttributeError as e:
143            reporter.messages.append(
144                (-1, 'Could not finish the parsing: %s.' % e, '', {}))
145
146        return reporter.messages
147