• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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