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