text: ## @file
  #  Retrieves the people to request review from on submission of a commit.
  #
  #  Copyright (c) 2019, Linaro Ltd. All rights reserved.<BR>
  #
  #  SPDX-License-Identifier: BSD-2-Clause-Patent
  #
  
  from __future__ import print_function
  from collections import defaultdict
  from collections import OrderedDict
  import argparse
  import os
  import re
  import SetupGit
  
  EXPRESSIONS = {
      'exclude':    re.compile(r'^X:\s*(?P<exclude>.*?)\r*$'),
      'file':       re.compile(r'^F:\s*(?P<file>.*?)\r*$'),
      'list':       re.compile(r'^L:\s*(?P<list>.*?)\r*$'),
      'maintainer': re.compile(r'^M:\s*(?P<maintainer>.*<.*?>)\r*$'),
      'reviewer':   re.compile(r'^R:\s*(?P<reviewer>.*?)\r*$'),
      'status':     re.compile(r'^S:\s*(?P<status>.*?)\r*$'),
      'tree':       re.compile(r'^T:\s*(?P<tree>.*?)\r*$'),
      'webpage':    re.compile(r'^W:\s*(?P<webpage>.*?)\r*$')
  }
  
  def printsection(section):
      """Prints out the dictionary describing a Maintainers.txt section."""
      print('===')
      for key in section.keys():
          print("Key: %s" % key)
          for item in section[key]:
              print('  %s' % item)
  
  def pattern_to_regex(pattern):
      """Takes a string containing regular UNIX path wildcards
         and returns a string suitable for matching with regex."""
  
      pattern = pattern.replace('.', r'\.')
      pattern = pattern.replace('?', r'.')
      pattern = pattern.replace('*', r'.*')
  
      if pattern.endswith('/'):
          pattern += r'.*'
      elif pattern.endswith('.*'):
          pattern = pattern[:-2]
          pattern += r'(?!.*?/.*?)'
  
      return pattern
  
  def path_in_section(path, section):
      """Returns True of False indicating whether the path is covered by
         the current section."""
      if not 'file' in section:
          return False
  
      for pattern in section['file']:
          regex = pattern_to_regex(pattern)
  
          match = re.match(regex, path)
          if match:
              # Check if there is an exclude pattern that applies
              for pattern in section['exclude']:
                  regex = pattern_to_regex(pattern)
  
                  match = re.match(regex, path)
                  if match:
                      return False
  
              return True
  
      return False
  
  def get_section_maintainers(path, section):
      """Returns a list with email addresses to any M: and R: entries
         matching the provided path in the provided section."""
      maintainers = []
      lists = []
      nowarn_status = ['Supported', 'Maintained']
  
      if path_in_section(path, section):
          for status in section['status']:
              if status not in nowarn_status:
                  print('WARNING: Maintained status for "%s" is \'%s\'!' % (path, status))
          for address in section['maintainer'], section['reviewer']:
              # Convert to list if necessary
              if isinstance(address, list):
                  maintainers += address
              else:
                  lists += [address]
          for address in section['list']:
              # Convert to list if necessary
              if isinstance(address, list):
                  lists += address
              else:
                  lists += [address]
  
      return maintainers, lists
  
  def get_maintainers(path, sections, level=0):
      """For 'path', iterates over all sections, returning maintainers
         for matching ones."""
      maintainers = []
      lists = []
      for section in sections:
          tmp_maint, tmp_lists = get_section_maintainers(path, section)
          if tmp_maint:
              maintainers += tmp_maint
          if tmp_lists:
              lists += tmp_lists
  
      if not maintainers:
          # If no match found, look for match for (nonexistent) file
          # REPO.working_dir/<default>
          print('"%s": no maintainers found, looking for default' % path)
          if level == 0:
              maintainers = get_maintainers('<default>', sections, level=level + 1)
          else:
              print("No <default> maintainers set for project.")
          if not maintainers:
              return None
  
      return maintainers + lists
  
  def parse_maintainers_line(line):
      """Parse one line of Maintainers.txt, returning any match group and its key."""
      for key, expression in EXPRESSIONS.items():
          match = expression.match(line)
          if match:
              return key, match.group(key)
      return None, None
  
  def parse_maintainers_file(filename):
      """Parse the Maintainers.txt from top-level of repo and
         return a list containing dictionaries of all sections."""
      with open(filename, 'r') as text:
          line = text.readline()
          sectionlist = []
          section = defaultdict(list)
          while line:
              key, value = parse_maintainers_line(line)
              if key and value:
                  section[key].append(value)
  
              line = text.readline()
              # If end of section (end of file, or non-tag line encountered)...
              if not key or not value or not line:
                  # ...if non-empty, append section to list.
                  if section:
                      sectionlist.append(section.copy())
                      section.clear()
  
          return sectionlist
  
  def get_modified_files(repo, args):
      """Returns a list of the files modified by the commit specified in 'args'."""
      commit = repo.commit(args.commit)
      return commit.stats.files
  
  if __name__ == '__main__':
      PARSER = argparse.ArgumentParser(
          description='Retrieves information on who to cc for review on a given commit')
      PARSER.add_argument('commit',
                          action="store",
                          help='git revision to examine (default: HEAD)',
                          nargs='?',
                          default='HEAD')
      PARSER.add_argument('-l', '--lookup',
                          help='Find section matches for path LOOKUP',
                          required=False)
      ARGS = PARSER.parse_args()
  
      REPO = SetupGit.locate_repo()
  
      CONFIG_FILE = os.path.join(REPO.working_dir, 'Maintainers.txt')
  
      SECTIONS = parse_maintainers_file(CONFIG_FILE)
  
      if ARGS.lookup:
          FILES = [ARGS.lookup]
      else:
          FILES = get_modified_files(REPO, ARGS)
  
      ADDRESSES = []
  
      for file in FILES:
          print(file)
          addresslist = get_maintainers(file, SECTIONS)
          if addresslist:
              ADDRESSES += addresslist
  
      for address in list(OrderedDict.fromkeys(ADDRESSES)):
          print('  %s' % address)
  