1"""distutils.command.check 2 3Implements the Distutils 'check' command. 4""" 5from distutils.core import Command 6from distutils.errors import DistutilsSetupError 7 8try: 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 15 class SilentReporter(Reporter): 16 17 def __init__(self, source, report_level, halt_level, stream=None, 18 debug=0, encoding='ascii', error_handler='replace'): 19 self.messages = [] 20 Reporter.__init__(self, source, report_level, halt_level, stream, 21 debug, encoding, error_handler) 22 23 def system_message(self, level, message, *children, **kwargs): 24 self.messages.append((level, message, children, kwargs)) 25 return nodes.system_message(message, level=level, 26 type=self.levels[level], 27 *children, **kwargs) 28 29 HAS_DOCUTILS = True 30except Exception: 31 # Catch all exceptions because exceptions besides ImportError probably 32 # indicate that docutils is not ported to Py3k. 33 HAS_DOCUTILS = False 34 35class check(Command): 36 """This command checks the meta-data of the package. 37 """ 38 description = ("perform some checks on the package") 39 user_options = [('metadata', 'm', 'Verify meta-data'), 40 ('restructuredtext', 'r', 41 ('Checks if long string meta-data syntax ' 42 'are reStructuredText-compliant')), 43 ('strict', 's', 44 'Will exit with an error if a check fails')] 45 46 boolean_options = ['metadata', 'restructuredtext', 'strict'] 47 48 def initialize_options(self): 49 """Sets default values for options.""" 50 self.restructuredtext = 0 51 self.metadata = 1 52 self.strict = 0 53 self._warnings = 0 54 55 def finalize_options(self): 56 pass 57 58 def warn(self, msg): 59 """Counts the number of warnings that occurs.""" 60 self._warnings += 1 61 return Command.warn(self, msg) 62 63 def run(self): 64 """Runs the command.""" 65 # perform the various tests 66 if self.metadata: 67 self.check_metadata() 68 if self.restructuredtext: 69 if HAS_DOCUTILS: 70 self.check_restructuredtext() 71 elif self.strict: 72 raise DistutilsSetupError('The docutils package is needed.') 73 74 # let's raise an error in strict mode, if we have at least 75 # one warning 76 if self.strict and self._warnings > 0: 77 raise DistutilsSetupError('Please correct your package.') 78 79 def check_metadata(self): 80 """Ensures that all required elements of meta-data are supplied. 81 82 Required fields: 83 name, version, URL 84 85 Recommended fields: 86 (author and author_email) or (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' should 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' should be supplied too") 107 else: 108 self.warn("missing meta-data: either (author and author_email) " + 109 "or (maintainer and maintainer_email) " + 110 "should 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 for warning in self._check_rst_data(data): 116 line = warning[-1].get('line') 117 if line is None: 118 warning = warning[1] 119 else: 120 warning = '%s (line %s)' % (warning[1], line) 121 self.warn(warning) 122 123 def _check_rst_data(self, data): 124 """Returns warnings when the provided data doesn't compile.""" 125 # the include and csv_table directives need this to be a path 126 source_path = self.distribution.script_name or 'setup.py' 127 parser = Parser() 128 settings = frontend.OptionParser(components=(Parser,)).get_default_values() 129 settings.tab_width = 4 130 settings.pep_references = None 131 settings.rfc_references = None 132 reporter = SilentReporter(source_path, 133 settings.report_level, 134 settings.halt_level, 135 stream=settings.warning_stream, 136 debug=settings.debug, 137 encoding=settings.error_encoding, 138 error_handler=settings.error_encoding_error_handler) 139 140 document = nodes.document(settings, reporter, source=source_path) 141 document.note_source(source_path, -1) 142 try: 143 parser.parse(data, document) 144 except AttributeError as e: 145 reporter.messages.append( 146 (-1, 'Could not finish the parsing: %s.' % e, '', {})) 147 148 return reporter.messages 149