__init__.py 13 KB

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