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