1# Copyright 2015-2017 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Interface to file resources. 15 16This module provides functions for interfacing with files: opening, writing, and 17querying. 18""" 19 20import fnmatch 21import os 22import re 23 24from lib2to3.pgen2 import tokenize 25 26from yapf.yapflib import errors 27from yapf.yapflib import py3compat 28from yapf.yapflib import style 29 30CR = '\r' 31LF = '\n' 32CRLF = '\r\n' 33 34 35def GetDefaultStyleForDir(dirname): 36 """Return default style name for a given directory. 37 38 Looks for .style.yapf or setup.cfg in the parent directories. 39 40 Arguments: 41 dirname: (unicode) The name of the directory. 42 43 Returns: 44 The filename if found, otherwise return the global default (pep8). 45 """ 46 dirname = os.path.abspath(dirname) 47 while True: 48 # See if we have a .style.yapf file. 49 style_file = os.path.join(dirname, style.LOCAL_STYLE) 50 if os.path.exists(style_file): 51 return style_file 52 53 # See if we have a setup.cfg file with a '[yapf]' section. 54 config_file = os.path.join(dirname, style.SETUP_CONFIG) 55 if os.path.exists(config_file): 56 with open(config_file) as fd: 57 config = py3compat.ConfigParser() 58 config.read_file(fd) 59 if config.has_section('yapf'): 60 return config_file 61 62 dirname = os.path.dirname(dirname) 63 if (not dirname or not os.path.basename(dirname) or 64 dirname == os.path.abspath(os.path.sep)): 65 break 66 67 global_file = os.path.expanduser(style.GLOBAL_STYLE) 68 if os.path.exists(global_file): 69 return global_file 70 71 return style.DEFAULT_STYLE 72 73 74def GetCommandLineFiles(command_line_file_list, recursive, exclude): 75 """Return the list of files specified on the command line.""" 76 return _FindPythonFiles(command_line_file_list, recursive, exclude) 77 78 79def WriteReformattedCode(filename, 80 reformatted_code, 81 in_place=False, 82 encoding=''): 83 """Emit the reformatted code. 84 85 Write the reformatted code into the file, if in_place is True. Otherwise, 86 write to stdout. 87 88 Arguments: 89 filename: (unicode) The name of the unformatted file. 90 reformatted_code: (unicode) The reformatted code. 91 in_place: (bool) If True, then write the reformatted code to the file. 92 encoding: (unicode) The encoding of the file. 93 """ 94 if in_place: 95 with py3compat.open_with_encoding( 96 filename, mode='w', encoding=encoding, newline='') as fd: 97 fd.write(reformatted_code) 98 else: 99 py3compat.EncodeAndWriteToStdout(reformatted_code) 100 101 102def LineEnding(lines): 103 """Retrieve the line ending of the original source.""" 104 endings = {CRLF: 0, CR: 0, LF: 0} 105 for line in lines: 106 if line.endswith(CRLF): 107 endings[CRLF] += 1 108 elif line.endswith(CR): 109 endings[CR] += 1 110 elif line.endswith(LF): 111 endings[LF] += 1 112 return (sorted(endings, key=endings.get, reverse=True) or [LF])[0] 113 114 115def _FindPythonFiles(filenames, recursive, exclude): 116 """Find all Python files.""" 117 python_files = [] 118 for filename in filenames: 119 if os.path.isdir(filename): 120 if recursive: 121 # TODO(morbo): Look into a version of os.walk that can handle recursion. 122 python_files.extend( 123 os.path.join(dirpath, f) 124 for dirpath, _, filelist in os.walk(filename) for f in filelist 125 if IsPythonFile(os.path.join(dirpath, f))) 126 else: 127 raise errors.YapfError( 128 "directory specified without '--recursive' flag: %s" % filename) 129 elif os.path.isfile(filename): 130 python_files.append(filename) 131 132 if exclude: 133 return [ 134 f for f in python_files 135 if not any(fnmatch.fnmatch(f, p) for p in exclude) 136 ] 137 138 return python_files 139 140 141def IsPythonFile(filename): 142 """Return True if filename is a Python file.""" 143 if os.path.splitext(filename)[1] == '.py': 144 return True 145 146 try: 147 with open(filename, 'rb') as fd: 148 encoding = tokenize.detect_encoding(fd.readline)[0] 149 150 # Check for correctness of encoding. 151 with py3compat.open_with_encoding( 152 filename, mode='r', encoding=encoding) as fd: 153 fd.read() 154 except UnicodeDecodeError: 155 encoding = 'latin-1' 156 except (IOError, SyntaxError): 157 # If we fail to detect encoding (or the encoding cookie is incorrect - which 158 # will make detect_encoding raise SyntaxError), assume it's not a Python 159 # file. 160 return False 161 162 try: 163 with py3compat.open_with_encoding( 164 filename, mode='r', encoding=encoding) as fd: 165 first_line = fd.readlines()[0] 166 except (IOError, IndexError): 167 return False 168 169 return re.match(r'^#!.*\bpython[23]?\b', first_line) 170