content: # Copyright 2015 Google Inc. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  """Interface to file resources.
  
  This module provides functions for interfacing with files: opening, writing, and
  querying.
  """
  
  import codecs
  import fnmatch
  import os
  import re
  import sys
  from configparser import ConfigParser
  from tokenize import detect_encoding
  
  from yapf.yapflib import errors
  from yapf.yapflib import style
  
  if sys.version_info >= (3, 11):
    import tomllib
  else:
    import tomli as tomllib
  
  CR = '\r'
  LF = '\n'
  CRLF = '\r\n'
  
  
  def _GetExcludePatternsFromYapfIgnore(filename):
    """Get a list of file patterns to ignore from .yapfignore."""
    ignore_patterns = []
    if os.path.isfile(filename) and os.access(filename, os.R_OK):
      with open(filename, 'r') as fd:
        for line in fd:
          if line.strip() and not line.startswith('#'):
            ignore_patterns.append(line.strip())
  
      if any(e.startswith('./') for e in ignore_patterns):
        raise errors.YapfError('path in .yapfignore should not start with ./')
  
    return ignore_patterns
  
  
  def _GetExcludePatternsFromPyprojectToml(filename):
    """Get a list of file patterns to ignore from pyproject.toml."""
    ignore_patterns = []
  
    if os.path.isfile(filename) and os.access(filename, os.R_OK):
      with open(filename, 'rb') as fd:
        pyproject_toml = tomllib.load(fd)
      ignore_patterns = pyproject_toml.get('tool',
                                           {}).get('yapfignore',
                                                   {}).get('ignore_patterns', [])
      if any(e.startswith('./') for e in ignore_patterns):
        raise errors.YapfError('path in pyproject.toml should not start with ./')
  
    return ignore_patterns
  
  
  def GetExcludePatternsForDir(dirname):
    """Return patterns of files to exclude from ignorefile in a given directory.
  
    Looks for .yapfignore in the directory dirname.
  
    Arguments:
      dirname: (unicode) The name of the directory.
  
    Returns:
      A List of file patterns to exclude if ignore file is found, otherwise empty
      List.
    """
    ignore_patterns = []
  
    yapfignore_file = os.path.join(dirname, '.yapfignore')
    if os.path.exists(yapfignore_file):
      ignore_patterns += _GetExcludePatternsFromYapfIgnore(yapfignore_file)
  
    pyproject_toml_file = os.path.join(dirname, 'pyproject.toml')
    if os.path.exists(pyproject_toml_file):
      ignore_patterns += _GetExcludePatternsFromPyprojectToml(pyproject_toml_file)
    return ignore_patterns
  
  
  def GetDefaultStyleForDir(dirname, default_style=style.DEFAULT_STYLE):
    """Return default style name for a given directory.
  
    Looks for .style.yapf or setup.cfg or pyproject.toml in the parent
    directories.
  
    Arguments:
      dirname: (unicode) The name of the directory.
      default_style: The style to return if nothing is found. Defaults to the
                     global default style ('pep8') unless otherwise specified.
  
    Returns:
      The filename if found, otherwise return the default style.
    """
    dirname = os.path.abspath(dirname)
    while True:
      # See if we have a .style.yapf file.
      style_file = os.path.join(dirname, style.LOCAL_STYLE)
      if os.path.exists(style_file):
        return style_file
  
      # See if we have a setup.cfg file with a '[yapf]' section.
      config_file = os.path.join(dirname, style.SETUP_CONFIG)
      try:
        fd = open(config_file)
      except IOError:
        pass  # It's okay if it's not there.
      else:
        with fd:
          config = ConfigParser()
          config.read_file(fd)
          if config.has_section('yapf'):
            return config_file
  
      # See if we have a pyproject.toml file with a '[tool.yapf]'  section.
      config_file = os.path.join(dirname, style.PYPROJECT_TOML)
      try:
        fd = open(config_file, 'rb')
      except IOError:
        pass  # It's okay if it's not there.
      else:
        with fd:
          pyproject_toml = tomllib.load(fd)
          style_dict = pyproject_toml.get('tool', {}).get('yapf', None)
          if style_dict is not None:
            return config_file
  
      if (not dirname or not os.path.basename(dirname) or
          dirname == os.path.abspath(os.path.sep)):
        break
      dirname = os.path.dirname(dirname)
  
    global_file = os.path.expanduser(style.GLOBAL_STYLE)
    if os.path.exists(global_file):
      return global_file
  
    return default_style
  
  
  def GetCommandLineFiles(command_line_file_list, recursive, exclude):
    """Return the list of files specified on the command line."""
    return _FindPythonFiles(command_line_file_list, recursive, exclude)
  
  
  def WriteReformattedCode(filename,
                           reformatted_code,
                           encoding='',
                           in_place=False):
    """Emit the reformatted code.
  
    Write the reformatted code into the file, if in_place is True. Otherwise,
    write to stdout.
  
    Arguments:
      filename: (unicode) The name of the unformatted file.
      reformatted_code: (unicode) The reformatted code.
      encoding: (unicode) The encoding of the file.
      in_place: (bool) If True, then write the reformatted code to the file.
    """
    if in_place:
      with codecs.open(filename, mode='w', encoding=encoding) as fd:
        fd.write(reformatted_code)
    else:
      sys.stdout.buffer.write(reformatted_code.encode('utf-8'))
  
  
  def LineEnding(lines):
    """Retrieve the line ending of the original source."""
    endings = {CRLF: 0, CR: 0, LF: 0}
    for line in lines:
      if line.endswith(CRLF):
        endings[CRLF] += 1
      elif line.endswith(CR):
        endings[CR] += 1
      elif line.endswith(LF):
        endings[LF] += 1
    return sorted((LF, CRLF, CR), key=endings.get, reverse=True)[0]
  
  
  def _FindPythonFiles(filenames, recursive, exclude):
    """Find all Python files."""
    if exclude and any(e.startswith('./') for e in exclude):
      raise errors.YapfError("path in '--exclude' should not start with ./")
    exclude = exclude and [e.rstrip('/' + os.path.sep) for e in exclude]
  
    python_files = []
    for filename in filenames:
      if filename != '.' and exclude and IsIgnored(filename, exclude):
        continue
      if os.path.isdir(filename):
        if not recursive:
          raise errors.YapfError(
              "directory specified without '--recursive' flag: %s" % filename)
  
        # TODO(morbo): Look into a version of os.walk that can handle recursion.
        excluded_dirs = []
        for dirpath, dirnames, filelist in os.walk(filename):
          if dirpath != '.' and exclude and IsIgnored(dirpath, exclude):
            excluded_dirs.append(dirpath)
            continue
          elif any(dirpath.startswith(e) for e in excluded_dirs):
            continue
          for f in filelist:
            filepath = os.path.join(dirpath, f)
            if exclude and IsIgnored(filepath, exclude):
              continue
            if IsPythonFile(filepath):
              python_files.append(filepath)
          # To prevent it from scanning the contents excluded folders, os.walk()
          # lets you amend its list of child dirs `dirnames`. These edits must be
          # made in-place instead of creating a modified copy of `dirnames`.
          # list.remove() is slow and list.pop() is a headache. Instead clear
          # `dirnames` then repopulate it.
          dirnames_ = [dirnames.pop(0) for i in range(len(dirnames))]
          for dirname in dirnames_:
            dir_ = os.path.join(dirpath, dirname)
            if IsIgnored(dir_, exclude):
              excluded_dirs.append(dir_)
            else:
              dirnames.append(dirname)
  
      elif os.path.isfile(filename):
        python_files.append(filename)
  
    return python_files
  
  
  def IsIgnored(path, exclude):
    """Return True if filename matches any patterns in exclude."""
    if exclude is None:
      return False
    path = path.lstrip(os.path.sep)
    while path.startswith('.' + os.path.sep):
      path = path[2:]
    return any(fnmatch.fnmatch(path, e.rstrip(os.path.sep)) for e in exclude)
  
  
  def IsPythonFile(filename):
    """Return True if filename is a Python file."""
    if os.path.splitext(filename)[1] in frozenset({'.py', '.pyi'}):
      return True
  
    try:
      with open(filename, 'rb') as fd:
        encoding = detect_encoding(fd.readline)[0]
  
      # Check for correctness of encoding.
      with codecs.open(filename, mode='r', encoding=encoding) as fd:
        fd.read()
    except UnicodeDecodeError:
      encoding = 'latin-1'
    except (IOError, SyntaxError):
      # If we fail to detect encoding (or the encoding cookie is incorrect - which
      # will make detect_encoding raise SyntaxError), assume it's not a Python
      # file.
      return False
  
    try:
      with codecs.open(filename, mode='r', encoding=encoding) as fd:
        first_line = fd.readline(256)
    except IOError:
      return False
  
    return re.match(r'^#!.*\bpython[23]?\b', first_line)
  
  
  def FileEncoding(filename):
    """Return the file's encoding."""
    with open(filename, 'rb') as fd:
      return detect_encoding(fd.readline)[0]
  