SYCL: Avoid using no_init read accessor in rocFFT
[alexxy/gromacs.git] / docs / doxygen / reporter.py
1 #!/usr/bin/env python3
2 #
3 # This file is part of the GROMACS molecular simulation package.
4 #
5 # Copyright (c) 2014,2018,2019, 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.
9 #
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.
14 #
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.
19 #
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.
24 #
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.
32 #
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.
35
36 import sys
37 import functools
38
39 from fnmatch import fnmatch
40
41 """Central issue reporting implementation.
42
43 This module implements a Reporter class that is used by other Python modules in
44 this directory to report issues.  This allows central customization of the
45 output format, and also a central implementation for redirecting/copying
46 the output into a log file.  This class also implements sorting for the
47 messages such that all issues from a single file are reported next to each
48 other in the output, as well as filtering to make it possible to suppress
49 certain messages.
50 """
51
52 @functools.total_ordering
53 class Location(object):
54
55     """Location for a reported message."""
56
57     def __init__(self, filename, line):
58         """Create a location with the given file and line number.
59
60         One or both of the parameters can be None, but filename should be
61         specified if line is.
62         """
63         self.filename = filename
64         self.line = line
65
66     def __bool__(self):
67         """Make empty locations False in boolean context."""
68         return self.filename is not None
69
70     def __str__(self):
71         """Format the location as a string."""
72         if self.line:
73             return '{0}:{1}'.format(self.filename, self.line)
74         elif self.filename:
75             return self.filename
76         else:
77             return '<unknown>'
78
79     def __eq__(self, other):
80         """Sort locations based on file name and line number."""
81         return self.filename == other.filename and self.line == other.line
82
83     def __lt__(self, other):
84         """Sort locations based on file name and line number."""
85         if self.filename != other.filename:
86             if other.filename is None:
87                 return False
88             if self.filename is None:
89                 return True
90             return self.filename < other.filename
91         else:
92             if not self.filename:
93                 return False
94             if other.line is None:
95                 return False
96             if self.line is None:
97                 return True
98             return self.line < other.line
99
100 @functools.total_ordering
101 class Message(object):
102
103     """Single reported message.
104
105     This class stores the contents of a reporter message for later output to
106     allow sorting the output messages reasonably by the reported location.
107     """
108
109     def __init__(self, message, details=None, filename=None, location=None):
110         """Create a message object.
111
112         The message parameter provides the actual text, while optional details
113         provides a list of extra lines that provide context information for the
114         error.  filename and location provide two alternative ways of
115         specifying the location of the issue:
116          - if filename is provided, the issue is reported in that file, without
117            a line number
118          - if location is provided, it should be a Location instance
119         """
120         if filename:
121             self.location = Location(filename, None)
122         elif location:
123             self.location = location
124         else:
125             self.location = Location(None, None)
126         self.message = message
127         self.details = details
128
129     def __eq__(self, other):
130         """Sort messages based on file name and line number."""
131         return self.location == other.location
132
133     def __lt__(self, other):
134         """Sort messages based on file name and line number."""
135         return self.location < other.location
136
137 class Filter(object):
138
139     """Filter expression to exclude messages."""
140
141     def __init__(self, filterline):
142         """Initialize a filter from a line in a filter file."""
143         self._orgline = filterline
144         filepattern, text = filterline.split(':', 1)
145         if filepattern == '*':
146             self._filematcher = lambda x: x is not None
147         elif filepattern:
148             self._filematcher = lambda x: x and fnmatch(x, '*/' + filepattern)
149         else:
150             self._filematcher = lambda x: x is None
151         self._textpattern = text.strip()
152         self._count = 0
153
154     def matches(self, message):
155         """Check whether the filter matches a message."""
156         if not self._filematcher(message.location.filename):
157             return False
158         if not fnmatch(message.message, self._textpattern):
159             return False
160         self._count += 1
161         return True
162
163     def get_match_count(self):
164         """Return the number of times this filter has matched."""
165         return self._count
166
167     def get_text(self):
168         """Return original line used to specify the filter."""
169         return self._orgline
170
171 class Reporter(object):
172
173     """Collect and write out issues found by checker scripts."""
174
175     def __init__(self, logfile=None, quiet=False):
176         """Initialize the reporter.
177
178         If logfile is set to a file name, all issues will be written to this
179         file in addition to stderr.
180
181         If quiet is set to True, the reporter will suppress all output.
182         """
183         self._logfp = None
184         if logfile:
185             self._logfp = open(logfile, 'w')
186         self._messages = []
187         self._filters = []
188         self._quiet = quiet
189         self._had_warnings = False
190
191     def _write(self, message):
192         """Implement actual message writing."""
193         wholemsg = ''
194         if message.location:
195             wholemsg += str(message.location) + ': '
196         wholemsg += message.message
197         if message.details:
198             wholemsg += '\n    ' + '\n    '.join(message.details)
199         wholemsg += '\n'
200         sys.stderr.write(wholemsg)
201         if self._logfp:
202             self._logfp.write(wholemsg)
203         self._had_warnings = True
204
205     def _report(self, message):
206         """Handle a single reporter message."""
207         if self._quiet:
208             return
209         for filterobj in self._filters:
210             if filterobj.matches(message):
211                 return
212         if not message.location:
213             self._write(message)
214         else:
215             self._messages.append(message)
216
217     def load_filters(self, filterfile):
218         """Load filters for excluding messages from a file."""
219         with open(filterfile, 'r') as fp:
220             for filterline in fp:
221                 filterline = filterline.strip()
222                 if not filterline or filterline.startswith('#'):
223                     continue
224                 self._filters.append(Filter(filterline))
225
226     def write_pending(self):
227         """Write out pending messages in sorted order."""
228         self._messages.sort()
229         for message in self._messages:
230             self._write(message)
231         self._messages = []
232
233     def report_unused_filters(self):
234         """Report filters that did not match any messages."""
235         for filterobj in self._filters:
236             if filterobj.get_match_count() == 0:
237                 # TODO: Consider adding the input filter file as location
238                 text = 'warning: unused filter: ' + filterobj.get_text()
239                 self._write(Message(text))
240
241     def had_warnings(self):
242         """Return true if any warnings have been reported."""
243         return self._had_warnings
244
245     def close_log(self):
246         """Close the log file if one exists."""
247         assert not self._messages
248         if self._logfp:
249             self._logfp.close()
250             self._logfp = None
251
252     def xml_assert(self, xmlpath, message):
253         """Report issues in Doxygen XML that violate assumptions in the script."""
254         self._report(Message('warning: ' + message, filename=xmlpath))
255
256     def input_error(self, message):
257         """Report issues in input files."""
258         self._report(Message('error: ' + message))
259
260     def file_error(self, fileobj, message):
261         """Report file-level issues."""
262         self._report(Message('error: ' + message,
263             location=fileobj.get_reporter_location()))
264
265     def code_issue(self, entity, message, details=None):
266         """Report an issue in a code construct (not documentation related)."""
267         self._report(Message('warning: ' + message, details,
268             location=entity.get_reporter_location()))
269
270     def cyclic_issue(self, message, details=None):
271         """Report a cyclic dependency issue."""
272         self._report(Message('warning: ' + message, details))
273
274     def doc_error(self, entity, message):
275         """Report an issue in documentation."""
276         self._report(Message('error: ' + entity.get_name() + ': ' + message,
277             location=entity.get_reporter_location()))
278
279     def doc_note(self, entity, message):
280         """Report a potential issue in documentation."""
281         self._report(Message('note: ' + entity.get_name() + ': ' + message,
282             location=entity.get_reporter_location()))