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"""YAPF. 15 16YAPF uses the algorithm in clang-format to figure out the "best" formatting for 17Python code. It looks at the program as a series of "unwrappable lines" --- 18i.e., lines which, if there were no column limit, we would place all tokens on 19that line. It then uses a priority queue to figure out what the best formatting 20is --- i.e., the formatting with the least penalty. 21 22It differs from tools like autopep8 and pep8ify in that it doesn't just look for 23violations of the style guide, but looks at the module as a whole, making 24formatting decisions based on what's the best format for each line. 25 26If no filenames are specified, YAPF reads the code from stdin. 27""" 28from __future__ import print_function 29 30import argparse 31import logging 32import os 33import sys 34 35from yapf.yapflib import errors 36from yapf.yapflib import file_resources 37from yapf.yapflib import py3compat 38from yapf.yapflib import style 39from yapf.yapflib import yapf_api 40 41__version__ = '0.16.2' 42 43 44def main(argv): 45 """Main program. 46 47 Arguments: 48 argv: command-line arguments, such as sys.argv (including the program name 49 in argv[0]). 50 51 Returns: 52 0 if there were no changes, non-zero otherwise. 53 54 Raises: 55 YapfError: if none of the supplied files were Python files. 56 """ 57 parser = argparse.ArgumentParser(description='Formatter for Python code.') 58 parser.add_argument( 59 '-v', 60 '--version', 61 action='store_true', 62 help='show version number and exit') 63 64 diff_inplace_group = parser.add_mutually_exclusive_group() 65 diff_inplace_group.add_argument( 66 '-d', 67 '--diff', 68 action='store_true', 69 help='print the diff for the fixed source') 70 diff_inplace_group.add_argument( 71 '-i', 72 '--in-place', 73 action='store_true', 74 help='make changes to files in place') 75 76 lines_recursive_group = parser.add_mutually_exclusive_group() 77 lines_recursive_group.add_argument( 78 '-r', 79 '--recursive', 80 action='store_true', 81 help='run recursively over directories') 82 lines_recursive_group.add_argument( 83 '-l', 84 '--lines', 85 metavar='START-END', 86 action='append', 87 default=None, 88 help='range of lines to reformat, one-based') 89 90 parser.add_argument( 91 '-e', 92 '--exclude', 93 metavar='PATTERN', 94 action='append', 95 default=None, 96 help='patterns for files to exclude from formatting') 97 parser.add_argument( 98 '--style', 99 action='store', 100 help=('specify formatting style: either a style name (for example "pep8" ' 101 'or "google"), or the name of a file with style settings. The ' 102 'default is pep8 unless a %s or %s file located in one of the ' 103 'parent directories of the source file (or current directory for ' 104 'stdin)' % (style.LOCAL_STYLE, style.SETUP_CONFIG))) 105 parser.add_argument( 106 '--style-help', 107 action='store_true', 108 help=('show style settings and exit; this output can be ' 109 'saved to .style.yapf to make your settings ' 110 'permanent')) 111 parser.add_argument( 112 '--no-local-style', 113 action='store_true', 114 help="don't search for local style definition") 115 parser.add_argument('--verify', action='store_true', help=argparse.SUPPRESS) 116 parser.add_argument( 117 '-p', 118 '--parallel', 119 action='store_true', 120 help=('Run yapf in parallel when formatting multiple files. Requires ' 121 'concurrent.futures in Python 2.X')) 122 123 parser.add_argument('files', nargs='*') 124 args = parser.parse_args(argv[1:]) 125 126 if args.version: 127 print('yapf {}'.format(__version__)) 128 return 0 129 130 if args.style_help: 131 style.SetGlobalStyle(style.CreateStyleFromConfig(args.style)) 132 print('[style]') 133 for option, docstring in sorted(style.Help().items()): 134 for line in docstring.splitlines(): 135 print('#', line and ' ' or '', line, sep='') 136 print(option.lower(), '=', style.Get(option), sep='') 137 print() 138 return 0 139 140 if args.lines and len(args.files) > 1: 141 parser.error('cannot use -l/--lines with more than one file') 142 143 lines = _GetLines(args.lines) if args.lines is not None else None 144 if not args.files: 145 # No arguments specified. Read code from stdin. 146 if args.in_place or args.diff: 147 parser.error('cannot use --in-place or --diff flags when reading ' 148 'from stdin') 149 150 original_source = [] 151 while True: 152 try: 153 # Use 'raw_input' instead of 'sys.stdin.read', because otherwise the 154 # user will need to hit 'Ctrl-D' more than once if they're inputting 155 # the program by hand. 'raw_input' throws an EOFError exception if 156 # 'Ctrl-D' is pressed, which makes it easy to bail out of this loop. 157 original_source.append(py3compat.raw_input()) 158 except EOFError: 159 break 160 161 style_config = args.style 162 if style_config is None and not args.no_local_style: 163 style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) 164 165 source = [line.rstrip() for line in original_source] 166 reformatted_source, _ = yapf_api.FormatCode( 167 py3compat.unicode('\n'.join(source) + '\n'), 168 filename='<stdin>', 169 style_config=style_config, 170 lines=lines, 171 verify=args.verify) 172 file_resources.WriteReformattedCode('<stdout>', reformatted_source) 173 return 0 174 175 files = file_resources.GetCommandLineFiles(args.files, args.recursive, 176 args.exclude) 177 if not files: 178 raise errors.YapfError('Input filenames did not match any python files') 179 180 FormatFiles( 181 files, 182 lines, 183 style_config=args.style, 184 no_local_style=args.no_local_style, 185 in_place=args.in_place, 186 print_diff=args.diff, 187 verify=args.verify, 188 parallel=args.parallel) 189 return 0 190 191 192def FormatFiles(filenames, 193 lines, 194 style_config=None, 195 no_local_style=False, 196 in_place=False, 197 print_diff=False, 198 verify=True, 199 parallel=False): 200 """Format a list of files. 201 202 Arguments: 203 filenames: (list of unicode) A list of files to reformat. 204 lines: (list of tuples of integers) A list of tuples of lines, [start, end], 205 that we want to format. The lines are 1-based indexed. This argument 206 overrides the 'args.lines'. It can be used by third-party code (e.g., 207 IDEs) when reformatting a snippet of code. 208 style_config: (string) Style name or file path. 209 no_local_style: (string) If style_config is None don't search for 210 directory-local style configuration. 211 in_place: (bool) Modify the files in place. 212 print_diff: (bool) Instead of returning the reformatted source, return a 213 diff that turns the formatted source into reformatter source. 214 verify: (bool) True if reformatted code should be verified for syntax. 215 parallel: (bool) True if should format multiple files in parallel. 216 217 Returns: 218 True if the source code changed in any of the files being formatted. 219 """ 220 changed = False 221 if parallel: 222 import multiprocessing # pylint: disable=g-import-not-at-top 223 import concurrent.futures # pylint: disable=g-import-not-at-top 224 workers = min(multiprocessing.cpu_count(), len(filenames)) 225 with concurrent.futures.ProcessPoolExecutor(workers) as executor: 226 future_formats = [ 227 executor.submit(_FormatFile, filename, lines, style_config, 228 no_local_style, in_place, print_diff, verify) 229 for filename in filenames 230 ] 231 for future in concurrent.futures.as_completed(future_formats): 232 changed |= future.result() 233 else: 234 for filename in filenames: 235 changed |= _FormatFile(filename, lines, style_config, no_local_style, 236 in_place, print_diff, verify) 237 return changed 238 239 240def _FormatFile(filename, 241 lines, 242 style_config=None, 243 no_local_style=False, 244 in_place=False, 245 print_diff=False, 246 verify=True): 247 logging.info('Reformatting %s', filename) 248 if style_config is None and not no_local_style: 249 style_config = ( 250 file_resources.GetDefaultStyleForDir(os.path.dirname(filename))) 251 try: 252 reformatted_code, encoding, has_change = yapf_api.FormatFile( 253 filename, 254 in_place=in_place, 255 style_config=style_config, 256 lines=lines, 257 print_diff=print_diff, 258 verify=verify, 259 logger=logging.warning) 260 if not in_place and reformatted_code: 261 file_resources.WriteReformattedCode(filename, reformatted_code, in_place, 262 encoding) 263 return has_change 264 except SyntaxError as e: 265 e.filename = filename 266 raise 267 268 269def _GetLines(line_strings): 270 """Parses the start and end lines from a line string like 'start-end'. 271 272 Arguments: 273 line_strings: (array of string) A list of strings representing a line 274 range like 'start-end'. 275 276 Returns: 277 A list of tuples of the start and end line numbers. 278 279 Raises: 280 ValueError: If the line string failed to parse or was an invalid line range. 281 """ 282 lines = [] 283 for line_string in line_strings: 284 # The 'list' here is needed by Python 3. 285 line = list(map(int, line_string.split('-', 1))) 286 if line[0] < 1: 287 raise errors.YapfError('invalid start of line range: %r' % line) 288 if line[0] > line[1]: 289 raise errors.YapfError('end comes before start in line range: %r', line) 290 lines.append(tuple(line)) 291 return lines 292 293 294def run_main(): # pylint: disable=invalid-name 295 try: 296 sys.exit(main(sys.argv)) 297 except errors.YapfError as e: 298 sys.stderr.write('yapf: ' + str(e) + '\n') 299 sys.exit(1) 300 301 302if __name__ == '__main__': 303 run_main() 304