def _FormatFile(filename, lines, style_config=None, no_local_style=False, in_place=False, print_diff=False, verify=False, quiet=False, verbose=False): """Format an individual file.""" if verbose and not quiet: print('Reformatting %s' % filename) if style_config is None and not no_local_style: style_config = file_resources.GetDefaultStyleForDir( os.path.dirname(filename)) try: reformatted_code, encoding, has_change = yapf_api.FormatFile( filename, in_place=in_place, style_config=style_config, lines=lines, print_diff=print_diff, verify=verify, logger=logging.warning) if not in_place and not quiet and reformatted_code: file_resources.WriteReformattedCode(filename, reformatted_code, encoding, in_place) return has_change except tokenize.TokenError as e: raise errors.YapfError('%s:%s:%s' % (filename, e.args[1][0], e.args[0])) except SyntaxError as e: e.filename = filename raise
def _FindPythonFiles(filenames, recursive, exclude): """Find all Python files.""" python_files = [] for filename in filenames: if os.path.isdir(filename): if recursive: # TODO(morbo): Look into a version of os.walk that can handle recursion. python_files.extend( os.path.join(dirpath, f) for dirpath, _, filelist in os.walk(filename) for f in filelist if IsPythonFile(os.path.join(dirpath, f))) else: raise errors.YapfError( "directory specified without '--recursive' flag: %s" % filename) elif os.path.isfile(filename): python_files.append(filename) if exclude: return [ f for f in python_files if not any(fnmatch.fnmatch(f, p) for p in exclude) ] return python_files
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 ./") exclude = exclude and [e.rstrip("/" + os.path.sep) for e in exclude] 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, dirnames, 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) # To prevent it from scanning the contents excluded folders, os.walk() # lets you amend its list of child dirs `dirnames`. These edits must be # made in-place instead of creating a modified copy of `dirnames`. # list.remove() is slow and list.pop() is a headache. Instead clear # `dirnames` then repopulate it. dirnames_ = [dirnames.pop(0) for i in range(len(dirnames))] for dirname in dirnames_: dir_ = os.path.join(dirpath, dirname) if IsIgnored(dir_, exclude): excluded_dirs.append(dir_) else: dirnames.append(dirname) elif os.path.isfile(filename): python_files.append(filename) return python_files
def _GetExcludePatternsFromPyprojectToml(filename): """Get a list of file patterns to ignore from pyproject.toml.""" ignore_patterns = [] try: import toml except ImportError: raise errors.YapfError( "toml package is needed for using pyproject.toml as a configuration file" ) if os.path.isfile(filename) and os.access(filename, os.R_OK): pyproject_toml = toml.load(filename) ignore_patterns = pyproject_toml.get('tool', {}).get('yapfignore', {}).get( 'ignore_patterns', []) if any(e.startswith('./') for e in ignore_patterns): raise errors.YapfError( 'path in pyproject.toml should not start with ./') return ignore_patterns
def _GetExcludePatternsFromFile(filename): ignore_patterns = [] # See if we have a .yapfignore file. if os.path.isfile(filename) and os.access(filename, os.R_OK): for line in open(filename, 'r').readlines(): 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 _GetLines(line_strings): """Parses the start and end lines from a line string like 'start-end'. Arguments: line_strings: (array of string) A list of strings representing a line range like 'start-end'. Returns: A list of tuples of the start and end line numbers. Raises: ValueError: If the line string failed to parse or was an invalid line range. """ lines = [] for line_string in line_strings: # The 'list' here is needed by Python 3. line = list(map(int, line_string.split('-', 1))) if line[0] < 1: raise errors.YapfError('invalid start of line range: %r' % line) if line[0] > line[1]: raise errors.YapfError('end comes before start in line range: %r', line) lines.append(tuple(line)) return lines
def _GetExcludePatternsFromYapfIgnore(filename): """Get a list of file patterns to ignore from .yapfignore.""" ignore_patterns = [] 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 FormatCode(unformatted_source, filename='<unknown>', style_config=None, lines=None, print_diff=False, verify=False): """Format a string of Python code. This provides an alternative entry point to YAPF. Arguments: unformatted_source: (unicode) The code to format. filename: (unicode) The name of the file being reformatted. style_config: (string) Either a style name or a path to a file that contains formatting style settings. If None is specified, use the default style as set in style.DEFAULT_STYLE_FACTORY lines: (list of tuples of integers) A list of tuples of lines, [start, end], that we want to format. The lines are 1-based indexed. It can be used by third-party code (e.g., IDEs) when reformatting a snippet of code rather than a whole file. print_diff: (bool) Instead of returning the reformatted source, return a diff that turns the formatted source into reformatter source. verify: (bool) True if reformatted code should be verified for syntax. Returns: Tuple of (reformatted_source, changed). reformatted_source conforms to the desired formatting style. changed is True if the source changed. """ try: tree = pytree_utils.ParseCodeToTree(unformatted_source) except Exception as e: e.filename = filename raise errors.YapfError(errors.FormatErrorMsg(e)) reformatted_source = FormatTree(tree, style_config=style_config, lines=lines, verify=verify) if unformatted_source == reformatted_source: return '' if print_diff else reformatted_source, False code_diff = _GetUnifiedDiff(unformatted_source, reformatted_source, filename=filename) if print_diff: return code_diff, code_diff.strip() != '' # pylint: disable=g-explicit-bool-comparison # noqa return reformatted_source, True
def _CreateConfigParserFromConfigFile(config_filename): """Read the file and return a ConfigParser object.""" if not os.path.exists(config_filename): # Provide a more meaningful error here. raise StyleConfigError( '"{0}" is not a valid style or file path'.format(config_filename)) with open(config_filename) as style_file: config = py3compat.ConfigParser() if config_filename.endswith(PYPROJECT_TOML): try: import toml except ImportError: raise errors.YapfError( "toml package is needed for using pyproject.toml as a configuration file" ) pyproject_toml = toml.load(style_file) style_dict = pyproject_toml.get("tool", {}).get("yapf", None) if style_dict is None: raise StyleConfigError( 'Unable to find section [tool.yapf] in {0}'.format( config_filename)) config.add_section('style') for k, v in style_dict.items(): config.set('style', k, str(v)) return config config.read_file(style_file) if config_filename.endswith(SETUP_CONFIG): if not config.has_section('yapf'): raise StyleConfigError( 'Unable to find section [yapf] in {0}'.format( config_filename)) return config if config_filename.endswith(LOCAL_STYLE): if not config.has_section('style'): raise StyleConfigError( 'Unable to find section [style] in {0}'.format( config_filename)) return config if not config.has_section('style'): raise StyleConfigError( 'Unable to find section [style] in {0}'.format( config_filename)) return config
def main(argv): """Main program. Arguments: argv: command-line arguments, such as sys.argv (including the program name in argv[0]). Returns: 0 if there were no changes, non-zero otherwise. Raises: YapfError: if none of the supplied files were Python files. """ parser = argparse.ArgumentParser(description='Formatter for Python code.') parser.add_argument( '-v', '--version', action='store_true', help='show version number and exit') diff_inplace_group = parser.add_mutually_exclusive_group() diff_inplace_group.add_argument( '-d', '--diff', action='store_true', help='print the diff for the fixed source') diff_inplace_group.add_argument( '-i', '--in-place', action='store_true', help='make changes to files in place') lines_recursive_group = parser.add_mutually_exclusive_group() lines_recursive_group.add_argument( '-r', '--recursive', action='store_true', help='run recursively over directories') lines_recursive_group.add_argument( '-l', '--lines', metavar='START-END', action='append', default=None, help='range of lines to reformat, one-based') parser.add_argument( '-e', '--exclude', metavar='PATTERN', action='append', default=None, help='patterns for files to exclude from formatting') parser.add_argument( '--style', action='store', help=('specify formatting style: either a style name (for example "pep8" ' 'or "google"), or the name of a file with style settings. The ' 'default is pep8 unless a %s or %s file located in one of the ' 'parent directories of the source file (or current directory for ' 'stdin)' % (style.LOCAL_STYLE, style.SETUP_CONFIG))) parser.add_argument( '--style-help', action='store_true', help=('show style settings and exit; this output can be ' 'saved to .style.yapf to make your settings ' 'permanent')) parser.add_argument( '--no-local-style', action='store_true', help="don't search for local style definition") parser.add_argument('--verify', action='store_true', help=argparse.SUPPRESS) parser.add_argument( '-p', '--parallel', action='store_true', help=('Run yapf in parallel when formatting multiple files. Requires ' 'concurrent.futures in Python 2.X')) parser.add_argument('files', nargs='*') args = parser.parse_args(argv[1:]) if args.version: print('yapf {}'.format(__version__)) return 0 if args.style_help: style.SetGlobalStyle(style.CreateStyleFromConfig(args.style)) print('[style]') for option, docstring in sorted(style.Help().items()): for line in docstring.splitlines(): print('#', line and ' ' or '', line, sep='') print(option.lower(), '=', style.Get(option), sep='') print() return 0 if args.lines and len(args.files) > 1: parser.error('cannot use -l/--lines with more than one file') lines = _GetLines(args.lines) if args.lines is not None else None if not args.files: # No arguments specified. Read code from stdin. if args.in_place or args.diff: parser.error('cannot use --in-place or --diff flags when reading ' 'from stdin') original_source = [] while True: try: # Use 'raw_input' instead of 'sys.stdin.read', because otherwise the # user will need to hit 'Ctrl-D' more than once if they're inputting # the program by hand. 'raw_input' throws an EOFError exception if # 'Ctrl-D' is pressed, which makes it easy to bail out of this loop. original_source.append(py3compat.raw_input()) except EOFError: break style_config = args.style if style_config is None and not args.no_local_style: style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) source = [line.rstrip() for line in original_source] reformatted_source, _ = yapf_api.FormatCode( py3compat.unicode('\n'.join(source) + '\n'), filename='<stdin>', style_config=style_config, lines=lines, verify=args.verify) file_resources.WriteReformattedCode('<stdout>', reformatted_source) return 0 files = file_resources.GetCommandLineFiles(args.files, args.recursive, args.exclude) if not files: raise errors.YapfError('Input filenames did not match any python files') FormatFiles( files, lines, style_config=args.style, no_local_style=args.no_local_style, in_place=args.in_place, print_diff=args.diff, verify=args.verify, parallel=args.parallel) return 0
def main(argv): """Main program. Arguments: argv: command-line arguments, such as sys.argv (including the program name in argv[0]). Returns: Zero on successful program termination, non-zero otherwise. With --diff: zero if there were no changes, non-zero otherwise. Raises: YapfError: if none of the supplied files were Python files. """ args = _ParseArguments(argv) if args.version: print('yapf {}'.format(__version__)) return 0 style_config = args.style if args.style_help: print_help(args) return 0 if args.lines and len(args.files) > 1: parser.error('cannot use -l/--lines with more than one file') lines = _GetLines(args.lines) if args.lines is not None else None if not args.files: # No arguments specified. Read code from stdin. if args.in_place or args.diff: parser.error('cannot use --in-place or --diff flags when reading ' 'from stdin') original_source = [] while True: if sys.stdin.closed: break try: # Use 'raw_input' instead of 'sys.stdin.read', because otherwise the # user will need to hit 'Ctrl-D' more than once if they're inputting # the program by hand. 'raw_input' throws an EOFError exception if # 'Ctrl-D' is pressed, which makes it easy to bail out of this loop. original_source.append(py3compat.raw_input()) except EOFError: break except KeyboardInterrupt: return 1 if style_config is None and not args.no_local_style: style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) source = [line.rstrip() for line in original_source] source[0] = py3compat.removeBOM(source[0]) try: reformatted_source, _ = yapf_api.FormatCode( py3compat.unicode('\n'.join(source) + '\n'), filename='<stdin>', style_config=style_config, lines=lines, verify=args.verify) except tokenize.TokenError as e: raise errors.YapfError('%s:%s' % (e.args[1][0], e.args[0])) file_resources.WriteReformattedCode('<stdout>', reformatted_source) return 0 # Get additional exclude patterns from ignorefile exclude_patterns_from_ignore_file = file_resources.GetExcludePatternsForDir( os.getcwd()) files = file_resources.GetCommandLineFiles( args.files, args.recursive, (args.exclude or []) + exclude_patterns_from_ignore_file) if not files: raise errors.YapfError( 'Input filenames did not match any python files') changed = FormatFiles(files, lines, style_config=args.style, no_local_style=args.no_local_style, in_place=args.in_place, print_diff=args.diff, verify=args.verify, parallel=args.parallel, quiet=args.quiet, verbose=args.verbose) return 1 if changed and (args.diff or args.quiet) else 0
def GetDefaultStyleForDir(dirname, default_style=style.DEFAULT_STYLE): """Return default style name for a given directory. Looks for .style.yapf or setup.cfg or pyproject.toml 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 # See if we have a pyproject.toml file with a '[tool.yapf]' section. config_file = os.path.join(dirname, style.PYPROJECT_TOML) try: fd = open(config_file) except IOError: pass # It's okay if it's not there. else: with fd: try: import toml except ImportError: raise errors.YapfError( "toml package is needed for using pyproject.toml as a configuration file" ) pyproject_toml = toml.load(config_file) style_dict = pyproject_toml.get('tool', {}).get('yapf', None) if style_dict is not None: 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 main(argv): """Main program. Arguments: argv: command-line arguments, such as sys.argv (including the program name in argv[0]). Returns: Zero on successful program termination, non-zero otherwise. With --diff: zero if there were no changes, non-zero otherwise. Raises: YapfError: if none of the supplied files were Python files. """ parser = argparse.ArgumentParser(description='Formatter for Python code.') parser.add_argument( '-v', '--version', action='store_true', help='show version number and exit') diff_inplace_quiet_group = parser.add_mutually_exclusive_group() diff_inplace_quiet_group.add_argument( '-d', '--diff', action='store_true', help='print the diff for the fixed source') diff_inplace_quiet_group.add_argument( '-i', '--in-place', action='store_true', help='make changes to files in place') diff_inplace_quiet_group.add_argument( '-q', '--quiet', action='store_true', help='output nothing and set return value') lines_recursive_group = parser.add_mutually_exclusive_group() lines_recursive_group.add_argument( '-r', '--recursive', action='store_true', help='run recursively over directories') lines_recursive_group.add_argument( '-l', '--lines', metavar='START-END', action='append', default=None, help='range of lines to reformat, one-based') parser.add_argument( '-e', '--exclude', metavar='PATTERN', action='append', default=None, help='patterns for files to exclude from formatting') parser.add_argument( '--style', action='store', help=('specify formatting style: either a style name (for example "pep8" ' 'or "google"), or the name of a file with style settings. The ' 'default is pep8 unless a %s or %s file located in the same ' 'directory as the source or one of its parent directories ' '(for stdin, the current directory is used).' % (style.LOCAL_STYLE, style.SETUP_CONFIG))) parser.add_argument( '--style-help', action='store_true', help=('show style settings and exit; this output can be ' 'saved to .style.yapf to make your settings ' 'permanent')) parser.add_argument( '--no-local-style', action='store_true', help="don't search for local style definition") parser.add_argument('--verify', action='store_true', help=argparse.SUPPRESS) parser.add_argument( '-p', '--parallel', action='store_true', help=('run yapf in parallel when formatting multiple files. Requires ' 'concurrent.futures in Python 2.X')) parser.add_argument( '-vv', '--verbose', action='store_true', help='print out file names while processing') parser.add_argument( 'files', nargs='*', help='reads from stdin when no files are specified.') args = parser.parse_args(argv[1:]) if args.version: print('yapf {}'.format(__version__)) return 0 style_config = args.style if args.style_help: print_help(args) return 0 if args.lines and len(args.files) > 1: parser.error('cannot use -l/--lines with more than one file') lines = _GetLines(args.lines) if args.lines is not None else None if not args.files: # No arguments specified. Read code from stdin. if args.in_place or args.diff: parser.error('cannot use --in-place or --diff flags when reading ' 'from stdin') original_source = [] while True: if sys.stdin.closed: break try: # Use 'raw_input' instead of 'sys.stdin.read', because otherwise the # user will need to hit 'Ctrl-D' more than once if they're inputting # the program by hand. 'raw_input' throws an EOFError exception if # 'Ctrl-D' is pressed, which makes it easy to bail out of this loop. original_source.append(py3compat.raw_input()) except EOFError: break except KeyboardInterrupt: return 1 if style_config is None and not args.no_local_style: style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) source = [line.rstrip() for line in original_source] source[0] = py3compat.removeBOM(source[0]) try: reformatted_source, _ = yapf_api.FormatCode( py3compat.unicode('\n'.join(source) + '\n'), filename='<stdin>', style_config=style_config, lines=lines, verify=args.verify) except tokenize.TokenError as e: raise errors.YapfError('%s:%s' % (e.args[1][0], e.args[0])) file_resources.WriteReformattedCode('<stdout>', reformatted_source) return 0 # Get additional exclude patterns from ignorefile exclude_patterns_from_ignore_file = file_resources.GetExcludePatternsForDir( os.getcwd()) files = file_resources.GetCommandLineFiles(args.files, args.recursive, (args.exclude or []) + exclude_patterns_from_ignore_file) if not files: raise errors.YapfError('Input filenames did not match any python files') changed = FormatFiles( files, lines, style_config=args.style, no_local_style=args.no_local_style, in_place=args.in_place, print_diff=args.diff, verify=args.verify, parallel=args.parallel, quiet=args.quiet, verbose=args.verbose) return 1 if changed and (args.diff or args.quiet) else 0