blob: 4b64e458bc2dd2cc4e5fa0a008732b18e609e835 [file] [log] [blame]
Tarek Ziadé942825f2009-04-11 14:55:07 +00001"""distutils.command.check
2
3Implements the Distutils 'check' command.
4"""
5__revision__ = "$Id$"
6
7from distutils.core import Command
Éric Araujo017e5352011-10-09 07:11:19 +02008from distutils.dist import PKG_INFO_ENCODING
Tarek Ziadé942825f2009-04-11 14:55:07 +00009from distutils.errors import DistutilsSetupError
10
11try:
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
30 HAS_DOCUTILS = True
31except ImportError:
32 # docutils is not installed
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:
Tarek Ziadéc2936b72009-04-11 15:14:17 +000069 if HAS_DOCUTILS:
Tarek Ziadé942825f2009-04-11 14:55:07 +000070 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
Tarek Ziadé783f4932009-04-17 14:29:56 +000076 if self.strict and self._warnings > 0:
Tarek Ziadé942825f2009-04-11 14:55:07 +000077 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 name, version, URL, (author and author_email) or
83 (maintainer and maintainer_email)).
84
85 Warns if any are missing.
86 """
87 metadata = self.distribution.metadata
88
89 missing = []
90 for attr in ('name', 'version', 'url'):
91 if not (hasattr(metadata, attr) and getattr(metadata, attr)):
92 missing.append(attr)
93
94 if missing:
Andrew M. Kuchlingb6f01282009-10-05 22:32:48 +000095 self.warn("missing required meta-data: %s" % ', '.join(missing))
Tarek Ziadé942825f2009-04-11 14:55:07 +000096 if metadata.author:
97 if not metadata.author_email:
98 self.warn("missing meta-data: if 'author' supplied, " +
99 "'author_email' must be supplied too")
100 elif metadata.maintainer:
101 if not metadata.maintainer_email:
102 self.warn("missing meta-data: if 'maintainer' supplied, " +
103 "'maintainer_email' must be supplied too")
104 else:
105 self.warn("missing meta-data: either (author and author_email) " +
106 "or (maintainer and maintainer_email) " +
107 "must be supplied")
108
109 def check_restructuredtext(self):
110 """Checks if the long string fields are reST-compliant."""
111 data = self.distribution.get_long_description()
Éric Araujo017e5352011-10-09 07:11:19 +0200112 if not isinstance(data, unicode):
113 data = data.decode(PKG_INFO_ENCODING)
Tarek Ziadé942825f2009-04-11 14:55:07 +0000114 for warning in self._check_rst_data(data):
115 line = warning[-1].get('line')
116 if line is None:
117 warning = warning[1]
118 else:
119 warning = '%s (line %s)' % (warning[1], line)
120 self.warn(warning)
121
122 def _check_rst_data(self, data):
123 """Returns warnings when the provided data doesn't compile."""
124 source_path = StringIO()
125 parser = Parser()
126 settings = frontend.OptionParser().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:
143 reporter.messages.append((-1, 'Could not finish the parsing.',
144 '', {}))
145
146 return reporter.messages