183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh"""distutils.command.check
283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew HsiehImplements the Distutils 'check' command.
483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh"""
583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh__revision__ = "$Id$"
683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsiehfrom distutils.core import Command
883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsiehfrom distutils.dist import PKG_INFO_ENCODING
983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsiehfrom distutils.errors import DistutilsSetupError
1083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
1183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsiehtry:
1283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    # docutils is installed
1383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    from docutils.utils import Reporter
1483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    from docutils.parsers.rst import Parser
1583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    from docutils import frontend
1683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    from docutils import nodes
1783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    from StringIO import StringIO
1883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
1983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    class SilentReporter(Reporter):
2083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
2183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        def __init__(self, source, report_level, halt_level, stream=None,
2283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                     debug=0, encoding='ascii', error_handler='replace'):
2383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            self.messages = []
2483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            Reporter.__init__(self, source, report_level, halt_level, stream,
2583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                              debug, encoding, error_handler)
2683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
2783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        def system_message(self, level, message, *children, **kwargs):
2883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            self.messages.append((level, message, children, kwargs))
2983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            return nodes.system_message(message, level=level,
3083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                                        type=self.levels[level],
3183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                                        *children, **kwargs)
3283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
3383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    HAS_DOCUTILS = True
3483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsiehexcept ImportError:
3583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    # docutils is not installed
3683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    HAS_DOCUTILS = False
3783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
3883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsiehclass check(Command):
3983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    """This command checks the meta-data of the package.
4083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    """
4183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    description = ("perform some checks on the package")
4283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    user_options = [('metadata', 'm', 'Verify meta-data'),
4383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                    ('restructuredtext', 'r',
4483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                     ('Checks if long string meta-data syntax '
4583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                      'are reStructuredText-compliant')),
4683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                    ('strict', 's',
4783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                     'Will exit with an error if a check fails')]
4883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
4983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    boolean_options = ['metadata', 'restructuredtext', 'strict']
5083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
5183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    def initialize_options(self):
5283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        """Sets default values for options."""
5383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        self.restructuredtext = 0
5483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        self.metadata = 1
5583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        self.strict = 0
5683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        self._warnings = 0
5783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
5883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    def finalize_options(self):
5983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        pass
6083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
6183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    def warn(self, msg):
6283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        """Counts the number of warnings that occurs."""
6383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        self._warnings += 1
6483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        return Command.warn(self, msg)
6583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
6683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    def run(self):
6783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        """Runs the command."""
6883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        # perform the various tests
6983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        if self.metadata:
7083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            self.check_metadata()
7183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        if self.restructuredtext:
7283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            if HAS_DOCUTILS:
7383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                self.check_restructuredtext()
7483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            elif self.strict:
7583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                raise DistutilsSetupError('The docutils package is needed.')
7683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
7783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        # let's raise an error in strict mode, if we have at least
7883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        # one warning
7983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        if self.strict and self._warnings > 0:
8083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            raise DistutilsSetupError('Please correct your package.')
8183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
8283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    def check_metadata(self):
8383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        """Ensures that all required elements of meta-data are supplied.
8483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
8583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        name, version, URL, (author and author_email) or
8683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        (maintainer and maintainer_email)).
8783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
8883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        Warns if any are missing.
8983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        """
9083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        metadata = self.distribution.metadata
9183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
9283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        missing = []
9383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        for attr in ('name', 'version', 'url'):
9483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            if not (hasattr(metadata, attr) and getattr(metadata, attr)):
9583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                missing.append(attr)
9683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
9783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        if missing:
9883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            self.warn("missing required meta-data: %s"  % ', '.join(missing))
9983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        if metadata.author:
10083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            if not metadata.author_email:
10183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                self.warn("missing meta-data: if 'author' supplied, " +
10283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          "'author_email' must be supplied too")
10383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        elif metadata.maintainer:
10483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            if not metadata.maintainer_email:
10583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                self.warn("missing meta-data: if 'maintainer' supplied, " +
10683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          "'maintainer_email' must be supplied too")
10783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        else:
10883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            self.warn("missing meta-data: either (author and author_email) " +
10983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                      "or (maintainer and maintainer_email) " +
11083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                      "must be supplied")
11183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
11283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    def check_restructuredtext(self):
11383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        """Checks if the long string fields are reST-compliant."""
11483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        data = self.distribution.get_long_description()
11583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        if not isinstance(data, unicode):
11683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            data = data.decode(PKG_INFO_ENCODING)
11783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        for warning in self._check_rst_data(data):
11883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            line = warning[-1].get('line')
11983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            if line is None:
12083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                warning = warning[1]
12183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            else:
12283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                warning = '%s (line %s)' % (warning[1], line)
12383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            self.warn(warning)
12483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
12583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh    def _check_rst_data(self, data):
12683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        """Returns warnings when the provided data doesn't compile."""
12783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        source_path = StringIO()
12883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        parser = Parser()
12983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        settings = frontend.OptionParser().get_default_values()
13083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        settings.tab_width = 4
13183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        settings.pep_references = None
13283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        settings.rfc_references = None
13383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        reporter = SilentReporter(source_path,
13483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          settings.report_level,
13583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          settings.halt_level,
13683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          stream=settings.warning_stream,
13783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          debug=settings.debug,
13883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          encoding=settings.error_encoding,
13983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                          error_handler=settings.error_encoding_error_handler)
14083760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
14183760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        document = nodes.document(settings, reporter, source=source_path)
14283760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        document.note_source(source_path, -1)
14383760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        try:
14483760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            parser.parse(data, document)
14583760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        except AttributeError:
14683760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh            reporter.messages.append((-1, 'Could not finish the parsing.',
14783760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh                                      '', {}))
14883760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh
14983760d213fb3bec7b4117d266fcfbf6fe2ba14abAndrew Hsieh        return reporter.messages
150