You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
7.0 KiB
230 lines
7.0 KiB
4 years ago
|
# 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 fnmatch
|
||
|
import os
|
||
|
import re
|
||
|
|
||
|
from lib2to3.pgen2 import tokenize
|
||
|
|
||
|
from yapf.yapflib import errors
|
||
|
from yapf.yapflib import py3compat
|
||
|
from yapf.yapflib import style
|
||
|
|
||
|
CR = '\r'
|
||
|
LF = '\n'
|
||
|
CRLF = '\r\n'
|
||
|
|
||
|
|
||
|
def _GetExcludePatternsFromFile(filename):
|
||
|
"""Get a list of file patterns to ignore."""
|
||
|
ignore_patterns = []
|
||
|
# See if we have a .yapfignore file.
|
||
|
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 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_file = os.path.join(dirname, '.yapfignore')
|
||
|
return _GetExcludePatternsFromFile(ignore_file)
|
||
|
|
||
|
|
||
|
def GetDefaultStyleForDir(dirname, default_style=style.DEFAULT_STYLE):
|
||
|
"""Return default style name for a given directory.
|
||
|
|
||
|
Looks for .style.yapf or setup.cfg 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 = py3compat.ConfigParser()
|
||
|
config.read_file(fd)
|
||
|
if config.has_section('yapf'):
|
||
|
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 py3compat.open_with_encoding(
|
||
|
filename, mode='w', encoding=encoding, newline='') as fd:
|
||
|
fd.write(reformatted_code)
|
||
|
else:
|
||
|
py3compat.EncodeAndWriteToStdout(reformatted_code)
|
||
|
|
||
|
|
||
|
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(endings, key=endings.get, reverse=True) or [LF])[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 ./")
|
||
|
|
||
|
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, _, 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)
|
||
|
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."""
|
||
|
path = path.lstrip('/')
|
||
|
while path.startswith('./'):
|
||
|
path = path[2:]
|
||
|
return any(fnmatch.fnmatch(path, e.rstrip('/')) 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 = tokenize.detect_encoding(fd.readline)[0]
|
||
|
|
||
|
# Check for correctness of encoding.
|
||
|
with py3compat.open_with_encoding(
|
||
|
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 py3compat.open_with_encoding(
|
||
|
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 tokenize.detect_encoding(fd.readline)[0]
|