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