check.py 5.4 KB

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