check.py 5.4 KB

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