text: #!/usr/bin/env python3
  #
  # This file is part of the GROMACS molecular simulation package.
  #
  # Copyright 2014- The GROMACS Authors
  # and the project initiators Erik Lindahl, Berk Hess and David van der Spoel.
  # Consult the AUTHORS/COPYING files and https://www.gromacs.org for details.
  #
  # GROMACS is free software; you can redistribute it and/or
  # modify it under the terms of the GNU Lesser General Public License
  # as published by the Free Software Foundation; either version 2.1
  # of the License, or (at your option) any later version.
  #
  # GROMACS is distributed in the hope that it will be useful,
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  # Lesser General Public License for more details.
  #
  # You should have received a copy of the GNU Lesser General Public
  # License along with GROMACS; if not, see
  # https://www.gnu.org/licenses, or write to the Free Software Foundation,
  # Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
  #
  # If you want to redistribute modifications to GROMACS, please
  # consider that scientific software is very special. Version
  # control is crucial - bugs must be traceable. We will be happy to
  # consider code for inclusion in the official distribution, but
  # derived work must not be called official GROMACS. Details are found
  # in the README & COPYING files - if they are missing, get the
  # official version at https://www.gromacs.org.
  #
  # To help us fund GROMACS development, we humbly ask that you cite
  # the research papers on the package. Check out https://www.gromacs.org.
  
  import sys
  import functools
  
  from fnmatch import fnmatch
  
  """Central issue reporting implementation.
  
  This module implements a Reporter class that is used by other Python modules in
  this directory to report issues.  This allows central customization of the
  output format, and also a central implementation for redirecting/copying
  the output into a log file.  This class also implements sorting for the
  messages such that all issues from a single file are reported next to each
  other in the output, as well as filtering to make it possible to suppress
  certain messages.
  """
  
  @functools.total_ordering
  class Location(object):
  
      """Location for a reported message."""
  
      def __init__(self, filename, line):
          """Create a location with the given file and line number.
  
          One or both of the parameters can be None, but filename should be
          specified if line is.
          """
          self.filename = filename
          self.line = line
  
      def __bool__(self):
          """Make empty locations False in boolean context."""
          return self.filename is not None
  
      def __str__(self):
          """Format the location as a string."""
          if self.line:
              return '{0}:{1}'.format(self.filename, self.line)
          elif self.filename:
              return self.filename
          else:
              return '<unknown>'
  
      def __eq__(self, other):
          """Sort locations based on file name and line number."""
          return self.filename == other.filename and self.line == other.line
  
      def __lt__(self, other):
          """Sort locations based on file name and line number."""
          if self.filename != other.filename:
              if other.filename is None:
                  return False
              if self.filename is None:
                  return True
              return self.filename < other.filename
          else:
              if not self.filename:
                  return False
              if other.line is None:
                  return False
              if self.line is None:
                  return True
              return self.line < other.line
  
  @functools.total_ordering
  class Message(object):
  
      """Single reported message.
  
      This class stores the contents of a reporter message for later output to
      allow sorting the output messages reasonably by the reported location.
      """
  
      def __init__(self, message, details=None, filename=None, location=None):
          """Create a message object.
  
          The message parameter provides the actual text, while optional details
          provides a list of extra lines that provide context information for the
          error.  filename and location provide two alternative ways of
          specifying the location of the issue:
           - if filename is provided, the issue is reported in that file, without
             a line number
           - if location is provided, it should be a Location instance
          """
          if filename:
              self.location = Location(filename, None)
          elif location:
              self.location = location
          else:
              self.location = Location(None, None)
          self.message = message
          self.details = details
  
      def __eq__(self, other):
          """Sort messages based on file name and line number."""
          return self.location == other.location
  
      def __lt__(self, other):
          """Sort messages based on file name and line number."""
          return self.location < other.location
  
  class Filter(object):
  
      """Filter expression to exclude messages."""
  
      def __init__(self, filterline):
          """Initialize a filter from a line in a filter file."""
          self._orgline = filterline
          filepattern, text = filterline.split(':', 1)
          if filepattern == '*':
              self._filematcher = lambda x: x is not None
          elif filepattern:
              self._filematcher = lambda x: x and fnmatch(x, '*/' + filepattern)
          else:
              self._filematcher = lambda x: x is None
          self._textpattern = text.strip()
          self._count = 0
  
      def matches(self, message):
          """Check whether the filter matches a message."""
          if not self._filematcher(message.location.filename):
              return False
          if not fnmatch(message.message, self._textpattern):
              return False
          self._count += 1
          return True
  
      def get_match_count(self):
          """Return the number of times this filter has matched."""
          return self._count
  
      def get_text(self):
          """Return original line used to specify the filter."""
          return self._orgline
  
  class Reporter(object):
  
      """Collect and write out issues found by checker scripts."""
  
      def __init__(self, logfile=None, quiet=False):
          """Initialize the reporter.
  
          If logfile is set to a file name, all issues will be written to this
          file in addition to stderr.
  
          If quiet is set to True, the reporter will suppress all output.
          """
          self._logfp = None
          if logfile:
              self._logfp = open(logfile, 'w')
          self._messages = []
          self._filters = []
          self._quiet = quiet
          self._had_warnings = False
  
      def _write(self, message):
          """Implement actual message writing."""
          wholemsg = ''
          if message.location:
              wholemsg += str(message.location) + ': '
          wholemsg += message.message
          if message.details:
              wholemsg += '\n    ' + '\n    '.join(message.details)
          wholemsg += '\n'
          sys.stderr.write(wholemsg)
          if self._logfp:
              self._logfp.write(wholemsg)
          self._had_warnings = True
  
      def _report(self, message):
          """Handle a single reporter message."""
          if self._quiet:
              return
          for filterobj in self._filters:
              if filterobj.matches(message):
                  return
          if not message.location:
              self._write(message)
          else:
              self._messages.append(message)
  
      def load_filters(self, filterfile):
          """Load filters for excluding messages from a file."""
          with open(filterfile, 'r') as fp:
              for filterline in fp:
                  filterline = filterline.strip()
                  if not filterline or filterline.startswith('#'):
                      continue
                  self._filters.append(Filter(filterline))
  
      def write_pending(self):
          """Write out pending messages in sorted order."""
          self._messages.sort()
          for message in self._messages:
              self._write(message)
          self._messages = []
  
      def report_unused_filters(self):
          """Report filters that did not match any messages."""
          for filterobj in self._filters:
              if filterobj.get_match_count() == 0:
                  # TODO: Consider adding the input filter file as location
                  text = 'warning: unused filter: ' + filterobj.get_text()
                  self._write(Message(text))
  
      def had_warnings(self):
          """Return true if any warnings have been reported."""
          return self._had_warnings
  
      def close_log(self):
          """Close the log file if one exists."""
          assert not self._messages
          if self._logfp:
              self._logfp.close()
              self._logfp = None
  
      def xml_assert(self, xmlpath, message):
          """Report issues in Doxygen XML that violate assumptions in the script."""
          self._report(Message('warning: ' + message, filename=xmlpath))
  
      def input_error(self, message):
          """Report issues in input files."""
          self._report(Message('error: ' + message))
  
      def file_error(self, fileobj, message):
          """Report file-level issues."""
          self._report(Message('error: ' + message,
              location=fileobj.get_reporter_location()))
  
      def code_issue(self, entity, message, details=None):
          """Report an issue in a code construct (not documentation related)."""
          self._report(Message('warning: ' + message, details,
              location=entity.get_reporter_location()))
  
      def cyclic_issue(self, message, details=None):
          """Report a cyclic dependency issue."""
          self._report(Message('warning: ' + message, details))
  
      def doc_error(self, entity, message):
          """Report an issue in documentation."""
          self._report(Message('error: ' + entity.get_name() + ': ' + message,
              location=entity.get_reporter_location()))
  
      def doc_note(self, entity, message):
          """Report a potential issue in documentation."""
          self._report(Message('note: ' + entity.get_name() + ': ' + message,
              location=entity.get_reporter_location()))
  