Remove t_topology from two preprocess tools
[gromacs.git] / docs / doxygen / reporter.py
blob40f70c648a6bf0df66f444a36219372bbf3abfe5
1 #!/usr/bin/env python2
3 # This file is part of the GROMACS molecular simulation package.
5 # Copyright (c) 2014,2018, by the GROMACS development team, led by
6 # Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 # and including many others, as listed in the AUTHORS file in the
8 # top-level source directory and at http://www.gromacs.org.
10 # GROMACS is free software; you can redistribute it and/or
11 # modify it under the terms of the GNU Lesser General Public License
12 # as published by the Free Software Foundation; either version 2.1
13 # of the License, or (at your option) any later version.
15 # GROMACS is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 # Lesser General Public License for more details.
20 # You should have received a copy of the GNU Lesser General Public
21 # License along with GROMACS; if not, see
22 # http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 # If you want to redistribute modifications to GROMACS, please
26 # consider that scientific software is very special. Version
27 # control is crucial - bugs must be traceable. We will be happy to
28 # consider code for inclusion in the official distribution, but
29 # derived work must not be called official GROMACS. Details are found
30 # in the README & COPYING files - if they are missing, get the
31 # official version at http://www.gromacs.org.
33 # To help us fund GROMACS development, we humbly ask that you cite
34 # the research papers on the package. Check out http://www.gromacs.org.
36 import sys
38 from fnmatch import fnmatch
40 """Central issue reporting implementation.
42 This module implements a Reporter class that is used by other Python modules in
43 this directory to report issues. This allows central customization of the
44 output format, and also a central implementation for redirecting/copying
45 the output into a log file. This class also implements sorting for the
46 messages such that all issues from a single file are reported next to each
47 other in the output, as well as filtering to make it possible to suppress
48 certain messages.
49 """
51 class Location(object):
53 """Location for a reported message."""
55 def __init__(self, filename, line):
56 """Create a location with the given file and line number.
58 One or both of the parameters can be None, but filename should be
59 specified if line is.
60 """
61 self.filename = filename
62 self.line = line
64 def __nonzero__(self):
65 """Make empty locations False in boolean context."""
66 return self.filename is not None
68 def __str__(self):
69 """Format the location as a string."""
70 if self.line:
71 return '{0}:{1}'.format(self.filename, self.line)
72 elif self.filename:
73 return self.filename
74 else:
75 return '<unknown>'
77 def __cmp__(self, other):
78 """Sort locations based on file name and line number."""
79 result = cmp(self.filename, other.filename)
80 if not self.filename or result != 0:
81 return result
82 return cmp(self.line, other.line)
84 class Message(object):
86 """Single reported message.
88 This class stores the contents of a reporter message for later output to
89 allow sorting the output messages reasonably by the reported location.
90 """
92 def __init__(self, message, details=None, filename=None, location=None):
93 """Create a message object.
95 The message parameter provides the actual text, while optional details
96 provides a list of extra lines that provide context information for the
97 error. filename and location provide two alternative ways of
98 specifying the location of the issue:
99 - if filename is provided, the issue is reported in that file, without
100 a line number
101 - if location is provided, it should be a Location instance
103 if filename:
104 self.location = Location(filename, None)
105 elif location:
106 self.location = location
107 else:
108 self.location = Location(None, None)
109 self.message = message
110 self.details = details
112 def __cmp__(self, other):
113 """Sort messages based on file name and line number."""
114 return cmp(self.location, other.location)
116 class Filter(object):
118 """Filter expression to exclude messages."""
120 def __init__(self, filterline):
121 """Initialize a filter from a line in a filter file."""
122 self._orgline = filterline
123 filepattern, text = filterline.split(':', 1)
124 if filepattern == '*':
125 self._filematcher = lambda x: x is not None
126 elif filepattern:
127 self._filematcher = lambda x: x and fnmatch(x, '*/' + filepattern)
128 else:
129 self._filematcher = lambda x: x is None
130 self._textpattern = text.strip()
131 self._count = 0
133 def matches(self, message):
134 """Check whether the filter matches a message."""
135 if not self._filematcher(message.location.filename):
136 return False
137 if not fnmatch(message.message, self._textpattern):
138 return False
139 self._count += 1
140 return True
142 def get_match_count(self):
143 """Return the number of times this filter has matched."""
144 return self._count
146 def get_text(self):
147 """Return original line used to specify the filter."""
148 return self._orgline
150 class Reporter(object):
152 """Collect and write out issues found by checker scripts."""
154 def __init__(self, logfile=None, quiet=False):
155 """Initialize the reporter.
157 If logfile is set to a file name, all issues will be written to this
158 file in addition to stderr.
160 If quiet is set to True, the reporter will suppress all output.
162 self._logfp = None
163 if logfile:
164 self._logfp = open(logfile, 'w')
165 self._messages = []
166 self._filters = []
167 self._quiet = quiet
168 self._had_warnings = False
170 def _write(self, message):
171 """Implement actual message writing."""
172 wholemsg = ''
173 if message.location:
174 wholemsg += str(message.location) + ': '
175 wholemsg += message.message
176 if message.details:
177 wholemsg += '\n ' + '\n '.join(message.details)
178 wholemsg += '\n'
179 sys.stderr.write(wholemsg)
180 if self._logfp:
181 self._logfp.write(wholemsg)
182 self._had_warnings = True
184 def _report(self, message):
185 """Handle a single reporter message."""
186 if self._quiet:
187 return
188 for filterobj in self._filters:
189 if filterobj.matches(message):
190 return
191 if not message.location:
192 self._write(message)
193 else:
194 self._messages.append(message)
196 def load_filters(self, filterfile):
197 """Load filters for excluding messages from a file."""
198 with open(filterfile, 'r') as fp:
199 for filterline in fp:
200 filterline = filterline.strip()
201 if not filterline or filterline.startswith('#'):
202 continue
203 self._filters.append(Filter(filterline))
205 def write_pending(self):
206 """Write out pending messages in sorted order."""
207 self._messages.sort()
208 for message in self._messages:
209 self._write(message)
210 self._messages = []
212 def report_unused_filters(self):
213 """Report filters that did not match any messages."""
214 for filterobj in self._filters:
215 if filterobj.get_match_count() == 0:
216 # TODO: Consider adding the input filter file as location
217 text = 'warning: unused filter: ' + filterobj.get_text()
218 self._write(Message(text))
220 def had_warnings(self):
221 """Return true if any warnings have been reported."""
222 return self._had_warnings
224 def close_log(self):
225 """Close the log file if one exists."""
226 assert not self._messages
227 if self._logfp:
228 self._logfp.close()
229 self._logfp = None
231 def xml_assert(self, xmlpath, message):
232 """Report issues in Doxygen XML that violate assumptions in the script."""
233 self._report(Message('warning: ' + message, filename=xmlpath))
235 def input_error(self, message):
236 """Report issues in input files."""
237 self._report(Message('error: ' + message))
239 def file_error(self, fileobj, message):
240 """Report file-level issues."""
241 self._report(Message('error: ' + message,
242 location=fileobj.get_reporter_location()))
244 def code_issue(self, entity, message, details=None):
245 """Report an issue in a code construct (not documentation related)."""
246 self._report(Message('warning: ' + message, details,
247 location=entity.get_reporter_location()))
249 def cyclic_issue(self, message, details=None):
250 """Report a cyclic dependency issue."""
251 self._report(Message('warning: ' + message, details))
253 def doc_error(self, entity, message):
254 """Report an issue in documentation."""
255 self._report(Message('error: ' + entity.get_name() + ': ' + message,
256 location=entity.get_reporter_location()))
258 def doc_note(self, entity, message):
259 """Report a potential issue in documentation."""
260 self._report(Message('note: ' + entity.get_name() + ': ' + message,
261 location=entity.get_reporter_location()))