1 | """distutils.command.check
|
---|
2 |
|
---|
3 | Implements the Distutils 'check' command.
|
---|
4 | """
|
---|
5 | __revision__ = "$Id$"
|
---|
6 |
|
---|
7 | from distutils.core import Command
|
---|
8 | from distutils.dist import PKG_INFO_ENCODING
|
---|
9 | from distutils.errors import DistutilsSetupError
|
---|
10 |
|
---|
11 | try:
|
---|
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 | return nodes.system_message(message, level=level,
|
---|
30 | type=self.levels[level],
|
---|
31 | *children, **kwargs)
|
---|
32 |
|
---|
33 | HAS_DOCUTILS = True
|
---|
34 | except ImportError:
|
---|
35 | # docutils is not installed
|
---|
36 | HAS_DOCUTILS = False
|
---|
37 |
|
---|
38 | class 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:
|
---|
72 | if HAS_DOCUTILS:
|
---|
73 | 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
|
---|
79 | if self.strict and self._warnings > 0:
|
---|
80 | 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:
|
---|
98 | self.warn("missing required meta-data: %s" % ', '.join(missing))
|
---|
99 | 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()
|
---|
115 | if not isinstance(data, unicode):
|
---|
116 | data = data.decode(PKG_INFO_ENCODING)
|
---|
117 | 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
|
---|