blob: 152bf0de98bf705261dc5b7a3ab9f5ed4ad82652 [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))
Éric Araujo2320fa02012-12-08 22:26:57 -050029 return nodes.system_message(message, level=level,
30 type=self.levels[level],
31 *children, **kwargs)
Tarek Ziadé942825f2009-04-11 14:55:07 +000032
33 HAS_DOCUTILS = True
34except ImportError:
35 # docutils is not installed
36 HAS_DOCUTILS = False
37
38class check(Command):
39 """This command checks the meta-data of the package.
40 """
41 description = ("perform some checks on the package")
42 user_options = [('metadata', 'm', 'Verify meta-data'),
43 ('restructuredtext', 'r',
44 ('Checks if long string meta-data syntax '
45 'are reStructuredText-compliant')),
46 ('strict', 's',
47 'Will exit with an error if a check fails')]
48
49 boolean_options = ['metadata', 'restructuredtext', 'strict']
50
51 def initialize_options(self):
52 """Sets default values for options."""
53 self.restructuredtext = 0
54 self.metadata = 1
55 self.strict = 0
56 self._warnings = 0
57
58 def finalize_options(self):
59 pass
60
61 def warn(self, msg):
62 """Counts the number of warnings that occurs."""
63 self._warnings += 1
64 return Command.warn(self, msg)
65
66 def run(self):
67 """Runs the command."""
68 # perform the various tests
69 if self.metadata:
70 self.check_metadata()
71 if self.restructuredtext:
Tarek Ziadéc2936b72009-04-11 15:14:17 +000072 if HAS_DOCUTILS:
Tarek Ziadé942825f2009-04-11 14:55:07 +000073 self.check_restructuredtext()
74 elif self.strict:
75 raise DistutilsSetupError('The docutils package is needed.')
76
77 # let's raise an error in strict mode, if we have at least
78 # one warning
Tarek Ziadé783f4932009-04-17 14:29:56 +000079 if self.strict and self._warnings > 0:
Tarek Ziadé942825f2009-04-11 14:55:07 +000080 raise DistutilsSetupError('Please correct your package.')
81
82 def check_metadata(self):
83 """Ensures that all required elements of meta-data are supplied.
84
85 name, version, URL, (author and author_email) or
86 (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:
Andrew M. Kuchlingb6f01282009-10-05 22:32:48 +000098 self.warn("missing required meta-data: %s" % ', '.join(missing))
Tarek Ziadé942825f2009-04-11 14:55:07 +000099 if metadata.author:
100 if not metadata.author_email:
101 self.warn("missing meta-data: if 'author' supplied, " +
102 "'author_email' must 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' must be supplied too")
107 else:
108 self.warn("missing meta-data: either (author and author_email) " +
109 "or (maintainer and maintainer_email) " +
110 "must 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()
Éric Araujo017e5352011-10-09 07:11:19 +0200115 if not isinstance(data, unicode):
116 data = data.decode(PKG_INFO_ENCODING)
Tarek Ziadé942825f2009-04-11 14:55:07 +0000117 for warning in self._check_rst_data(data):
118 line = warning[-1].get('line')
119 if line is None:
120 warning = warning[1]
121 else:
122 warning = '%s (line %s)' % (warning[1], line)
123 self.warn(warning)
124
125 def _check_rst_data(self, data):
126 """Returns warnings when the provided data doesn't compile."""
127 source_path = StringIO()
128 parser = Parser()
129 settings = frontend.OptionParser().get_default_values()
130 settings.tab_width = 4
131 settings.pep_references = None
132 settings.rfc_references = None
133 reporter = SilentReporter(source_path,
134 settings.report_level,
135 settings.halt_level,
136 stream=settings.warning_stream,
137 debug=settings.debug,
138 encoding=settings.error_encoding,
139 error_handler=settings.error_encoding_error_handler)
140
141 document = nodes.document(settings, reporter, source=source_path)
142 document.note_source(source_path, -1)
143 try:
144 parser.parse(data, document)
145 except AttributeError:
146 reporter.messages.append((-1, 'Could not finish the parsing.',
147 '', {}))
148
149 return reporter.messages