blob: 9a8fca1d5f2d9e2bf17d654c60cc77f568d683db [file] [log] [blame]
Tarek Ziadéf396ecf2009-04-11 15:00:43 +00001"""distutils.command.check
2
3Implements the Distutils 'check' command.
4"""
5__revision__ = "$Id$"
6
7from distutils.core import Command
8from distutils.errors import DistutilsSetupError
9
10try:
11 # docutils is installed
12 from docutils.utils import Reporter
13 from docutils.parsers.rst import Parser
14 from docutils import frontend
15 from docutils import nodes
16 from StringIO import StringIO
17
18 class SilentReporter(Reporter):
19
20 def __init__(self, source, report_level, halt_level, stream=None,
21 debug=0, encoding='ascii', error_handler='replace'):
22 self.messages = []
23 Reporter.__init__(self, source, report_level, halt_level, stream,
24 debug, encoding, error_handler)
25
26 def system_message(self, level, message, *children, **kwargs):
27 self.messages.append((level, message, children, kwargs))
28
29 HAS_DOCUTILS = True
Benjamin Petersonf47ed4a2009-04-11 20:45:40 +000030except Exception:
31 # Catch all exceptions because exceptions besides ImportError probably
32 # indicate that docutils is not ported to Py3k.
Tarek Ziadéf396ecf2009-04-11 15:00:43 +000033 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éaa4398b2009-04-11 15:17:04 +000069 if HAS_DOCUTILS:
Tarek Ziadéf396ecf2009-04-11 15:00:43 +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éf633ff42009-04-17 14:34:49 +000076 if self.strict and self._warnings > 0:
Tarek Ziadéf396ecf2009-04-11 15:00:43 +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:
95 self.warn("missing required meta-data: %s" % ' ,'.join(missing))
96 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()
112 for warning in self._check_rst_data(data):
113 line = warning[-1].get('line')
114 if line is None:
115 warning = warning[1]
116 else:
117 warning = '%s (line %s)' % (warning[1], line)
118 self.warn(warning)
119
120 def _check_rst_data(self, data):
121 """Returns warnings when the provided data doesn't compile."""
122 source_path = StringIO()
123 parser = Parser()
124 settings = frontend.OptionParser().get_default_values()
125 settings.tab_width = 4
126 settings.pep_references = None
127 settings.rfc_references = None
128 reporter = SilentReporter(source_path,
129 settings.report_level,
130 settings.halt_level,
131 stream=settings.warning_stream,
132 debug=settings.debug,
133 encoding=settings.error_encoding,
134 error_handler=settings.error_encoding_error_handler)
135
136 document = nodes.document(settings, reporter, source=source_path)
137 document.note_source(source_path, -1)
138 try:
139 parser.parse(data, document)
140 except AttributeError:
141 reporter.messages.append((-1, 'Could not finish the parsing.',
142 '', {}))
143
144 return reporter.messages