| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- # 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
- 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 = []
- try:
- import tomli as tomllib
- except ImportError:
- raise errors.YapfError(
- 'tomli package is needed for using pyproject.toml as a '
- 'configuration file')
- 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:
- try:
- import tomli as tomllib
- except ImportError:
- raise errors.YapfError(
- 'tomli package is needed for using pyproject.toml as a '
- 'configuration file')
- 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] == '.py':
- 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]
|