# Copyright (c) 2010 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""A database of OWNERS files."""

class Assertion(AssertionError):
  pass

class Database(object):
  def __init__(self, root, fopen, os_path):
    """Initializes the database of owners for a repository.

    Args:
      root: the path to the root of the Repository
      all_owners: the list of every owner in the system
      open: function callback to open a text file for reading
      os_path: module/object callback with fields for 'exists',
      'dirname', and 'join'
    """
    self.root = root
    self.fopen = fopen
    self.os_path = os_path

    # Mapping of files to authorized owners.
    self.files_owned_by = {}

    # Mapping of owners to the files they own.
    self.owners_for = {}

    # In-memory cached map of files to their OWNERS files.
    self.owners_file_for = {}

    # In-memory cache of OWNERS files and their contents
    self.owners_files = {}

  def OwnersFor(self, files):
    """Returns a sets of reviewers that will cover the set of files.

    The set of files are paths relative to (and under) self.root."""
    self._LoadDataNeededFor(files)
    return self._CoveringSetOfOwnersFor(files)

  def FilesAreCoveredBy(self, files, reviewers):
    return not self.FilesNotCoveredBy(files, reviewers)

  def FilesNotCoveredBy(self, files, reviewers):
    covered_files = set()
    for reviewer in reviewers:
      covered_files = covered_files.union(self.files_owned_by[reviewer])
    return files.difference(covered_files)

  def _LoadDataNeededFor(self, files):
    for f in files:
      self._LoadOwnersFor(f)

  def _LoadOwnersFor(self, f):
    if f not in self.owners_for:
      owner_file = self._FindOwnersFileFor(f)
      self.owners_file_for[f] = owner_file
      self._ReadOwnersFile(owner_file, f)

  def _FindOwnersFileFor(self, f):
    # This is really a "do ... until dirname = ''"
    dirname = self.os_path.dirname(f)
    while dirname:
      owner_path = self.os_path.join(dirname, 'OWNERS')
      if self.os_path.exists(owner_path):
        return owner_path
      dirname = self.os_path.dirname(dirname)
    owner_path = self.os_path.join(dirname, 'OWNERS')
    if self.os_path.exists(owner_path):
      return owner_path
    raise Assertion('No OWNERS file found for %s' % f)

  def _ReadOwnersFile(self, owner_file, affected_file):
    owners_for = self.owners_for.setdefault(affected_file, set())
    for owner in self.fopen(owner_file):
      owner = owner.strip()
      self.files_owned_by.setdefault(owner, set()).add(affected_file)
      owners_for.add(owner)

  def _CoveringSetOfOwnersFor(self, files):
    # TODO(dpranke): implement the greedy algorithm for covering sets, and
    # consider returning multiple options in case there are several equally
    # short combinations of owners.
    every_owner = set()
    for f in files:
      every_owner = every_owner.union(self.owners_for[f])
    return every_owner

@@ -240,141 +240,757 @@
 ss%0A%0A
+%0A
 class 
-Database(object):%0A  def __init__(self, root, fopen, os_path):%0A    %22%22%22Initializes the database of owners for a repository.%0A%0A    
+SyntaxErrorInOwnersFile(Exception):%0A  def __init__(self, path, line, msg):%0A    super(SyntaxErrorInOwnersFile, self).__init__((path, line, msg))%0A    self.path = path%0A    self.line = line%0A    self.msg = msg%0A%0A  def __str__(self):%0A    if self.msg:%0A      return %22%25s:%25d syntax error: %25s%22 %25 (self.path, self.line, self.msg)%0A    else:%0A      return %22%25s:%25d syntax error%22 %25 (self.path, self.line)%0A%0A%0A# Wildcard email-address in the OWNERS file.%0AANYONE = '*'%0A%0A%0Aclass Database(object):%0A  %22%22%22A database of OWNERS files for a repository.%0A%0A  This class allows you to find a suggested set of reviewers for a list%0A  of changed files, and see if a list of changed files is covered by a%0A  list of reviewers.%22%22%22%0A%0A  def __init__(self, root, fopen, os_path):%0A    %22%22%22
 Args
@@ -1230,16 +1230,18 @@
 ,%0A      
+  
 'dirname
@@ -1663,19 +1663,22 @@
 %0A%0A  def 
-Own
+Review
 ersFor(s
@@ -1708,19 +1708,28 @@
 urns a s
+uggested s
 et
-s
  of revi

rename OwnersFor() to ReviewersFor(), add SyntaxError exception, ANYONE wildcard constant