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