def main(args): """ This matches the default behaviour of the black formatter. This script will raise an exception if changes by black are required. """ sources = set() root = Path(os.getcwd()) p = Path(".") include_regex = re_compile_maybe_verbose(DEFAULT_INCLUDES) exclude_regex = re_compile_maybe_verbose(DEFAULT_EXCLUDES) report = Report() sources.update( gen_python_files_in_dir(p, root, include_regex, exclude_regex, report, get_gitignore(root))) # To conform to flake8 line length used for this project. mode = FileMode(line_length=79) write_back = WriteBack.from_configuration(check=False, diff=not args.force) reformat_many( sources=sources, fast=False, write_back=write_back, mode=mode, report=report, ) if report.change_count != 0: exception_msg = """ Black formatter suggests formatting changes required Run with '-f' option to automatically format. """ raise Exception(exception_msg)
def test_gitignore_paths_excluded(self, ): include = re.compile(r"(\.smk$|^Snakefile)") exclude = re.compile(r"") ignored_gitignore = get_gitignore(Path()) actual_gitignored = "Snakefile*" actual = self.find_files_to_format(include, exclude, ignored_gitignore, gitignored=actual_gitignored) expected = Counter(["rules/map.smk", "rules/test/test.smk"]) assert actual == expected
def main( ctx: click.Context, line_length: int, include: str, exclude: str, src: List[PathLike], verbose: bool, ): """The uncompromising Snakemake code formatter.""" log_level = logging.DEBUG if verbose else logging.INFO logging.basicConfig(format="[%(levelname)s] %(message)s", level=log_level) if not src: click.echo("No path provided. Nothing to do 😴", err=True) ctx.exit(0) if "-" in src and len(src) > 1: raise click.BadArgumentUsage("Cannot mix stdin (-) with other files") try: include_regex = construct_regex(include) except re.error: raise InvalidRegularExpression( f"Invalid regular expression for --include given: {include!r}") try: exclude_regex = construct_regex(exclude) except re.error: raise InvalidRegularExpression( f"Invalid regular expression for --exclude given: {exclude!r}") sources: Set[PathLike] = set() root = Path() gitignore = get_gitignore(Path()) for s in src: path = Path(s) if path.is_dir(): sources.update( get_snakefiles_in_dir(path, root, include_regex, exclude_regex, gitignore)) elif path.is_file() or s == "-": # if a file was explicitly given, we don't care about its extension sources.add(path) else: logging.warning(f"ignoring invalid path: {s}") print(f"Formatting:") for s in sources: print(s)
def find_files_to_format(self, include, exclude, gitignore, gitignored: Optional[str] = None): with tempfile.TemporaryDirectory() as tmpdir: abs_tmpdir = Path(tmpdir).resolve() self.create_temp_filesystem_in(abs_tmpdir) if gitignored is not None: with (abs_tmpdir / ".gitignore").open("w") as fout: fout.write(gitignored) gitignore = get_gitignore(abs_tmpdir) snakefiles = get_snakefiles_in_dir( path=abs_tmpdir, include=include, exclude=exclude, gitignore=gitignore, ) snakefiles = list( map(lambda p: str(p.relative_to(abs_tmpdir)), snakefiles)) return Counter(snakefiles)
def get_source_files(paths: Iterable[str]) -> Iterable[Path]: report = Report() root = find_project_root((f for f in paths)) sources: Set[Path] = set() for filename in paths: path = Path(filename) if path.is_dir(): sources.update( gen_python_files_in_dir( path=path, root=root, include=INCLUDES, exclude=EXCLUDES, report=report, gitignore=get_gitignore(root), )) elif path.is_file(): sources.add(path) else: print(f"Error: invalid path: {path}") exit(1) return sources
def collect_files(src, include, exclude): root = black.find_project_root(tuple(src)) gitignore = black.get_gitignore(root) report = black.Report() force_exclude = "" for path in src: if path.is_dir(): yield from gen_python_files( path.iterdir(), root, include, exclude, force_exclude, report, gitignore, ) elif path.is_file() or str(path) == "-": yield path else: print(f"invalid path: {path}", file=sys.stderr)
def collect_files(src, include, exclude, force_exclude): root = find_project_root(tuple(src)) gitignore = black.get_gitignore(root) report = black.Report() for path in src: if path.is_dir(): yield from gen_python_files( path.iterdir(), root, include, exclude, force_exclude, report, gitignore, ) elif str(path) == "-": yield path elif path.is_file(): normalized_path = black.normalize_path_maybe_ignore( path, root, report) if normalized_path is None: continue normalized_path = "/" + normalized_path # Hard-exclude any files that matches the `--force-exclude` regex. if force_exclude: force_exclude_match = force_exclude.search(normalized_path) else: force_exclude_match = None if force_exclude_match and force_exclude_match.group(0): report.path_ignored( path, "matches the --force-exclude regular expression") continue yield path else: print(f"invalid path: {path}", file=sys.stderr)
def main(src): # Path handling inspired on the implementation of Black: https://github.com/psf/black root = find_project_root(src) gitignore = get_gitignore(root) sources: Set[Path] = set() for s in src: p = Path(s) if p.is_dir(): sources.update(get_files_in_dir(p, root, gitignore)) elif p.is_file() or s == "-": # if a file was explicitly given, we don't care about its extension sources.add(p) else: raise RuntimeError(f"invalid path: {s}") ok = True for source in sources: file_ok = check_file(source) if not file_ok: ok = False if not ok: sys.exit(1)
def main( ctx: click.Context, line_length: int, check: bool, diff: bool, compact_diff: bool, include: str, exclude: str, src: List[PathLike], config: Optional[PathLike], verbose: bool, ): """The uncompromising Snakemake code formatter. SRC specifies directories and files to format. Directories will be searched for file names that conform to the include/exclude patterns provided. Files are modified in-place by default; use diff, check, or `snakefmt - < Snakefile` to avoid this. """ log_level = logging.DEBUG if verbose else logging.INFO logging.basicConfig(format="[%(levelname)s] %(message)s", level=log_level) if not src: click.echo( "No path provided. Nothing to do 😴. Call with -h for help.", err=True ) ctx.exit(0) if "-" in src and len(src) > 1: raise click.BadArgumentUsage("Cannot mix stdin (-) with other files") if diff and compact_diff: logging.warning( "Both --diff and --compact-diff given. Returning compact diff..." ) try: include_regex = construct_regex(include) except re.error: raise InvalidRegularExpression( f"Invalid regular expression for --include given: {include!r}" ) try: exclude_regex = construct_regex(exclude) except re.error: raise InvalidRegularExpression( f"Invalid regular expression for --exclude given: {exclude!r}" ) files_to_format: Set[PathLike] = set() gitignore = get_gitignore(Path()) for path in src: path = Path(path) if path.name == "-" or path.is_file(): # if a file was explicitly given, we don't care about its extension files_to_format.add(path) elif path.is_dir(): files_to_format.update( get_snakefiles_in_dir(path, include_regex, exclude_regex, gitignore) ) else: logging.warning(f"ignoring invalid path: {path}") differ = Diff(compact=compact_diff) files_changed, files_unchanged = 0, 0 files_with_errors = 0 for path in files_to_format: path_is_stdin = path.name == "-" if path_is_stdin: logging.debug("Formatting from stdin") path = sys.stdin else: logging.debug(f"Formatting {path}") try: original_content = path.read_text() except AttributeError: original_content = path.read() try: snakefile = Snakefile(StringIO(original_content)) formatter = Formatter( snakefile, line_length=line_length, black_config_file=config ) formatted_content = formatter.get_formatted() except Exception as error: if check: logging.error(f"'{error.__class__.__name__}: {error}' in file {path}") files_with_errors += 1 continue else: raise error if check: is_changed = differ.is_changed(original_content, formatted_content) if is_changed: logging.debug("Formatted content is different from original") files_changed += 1 else: files_unchanged += 1 if diff or compact_diff: filename = "stdin" if path_is_stdin else str(path) click.echo(f"{'=' * 5}> Diff for {filename} <{'=' * 5}\n") difference = differ.compare(original_content, formatted_content) click.echo(difference) elif not any([check, diff, compact_diff]): if path_is_stdin: sys.stdout.write(formatted_content) else: write_file_back = differ.is_changed(original_content, formatted_content) if write_file_back: logging.info(f"Writing formatted content to {path}") with path.open("w") as out_handle: out_handle.write(formatted_content) if check: if files_unchanged == len(files_to_format): logging.info( f"All {len(files_to_format)} file(s) would be left unchanged 🎉" ) ctx.exit(ExitCode.NO_CHANGE.value) elif files_with_errors > 0: exit_value = ExitCode.ERROR.value elif files_changed > 0: exit_value = ExitCode.WOULD_CHANGE.value if files_with_errors > 0: logging.info(f"{files_with_errors} file(s) raised parsing errors 🤕") if files_changed > 0: logging.info(f"{files_changed} file(s) would be changed 😬") if files_unchanged > 0: logging.info(f"{files_unchanged} file(s) would be left unchanged 🎉") ctx.exit(exit_value) logging.info("All done 🎉")
def main( ctx: click.Context, code: Optional[str], line_length: int, target_version: List[TargetVersion], check: bool, diff: bool, fast: bool, pyi: bool, py36: bool, skip_string_normalization: bool, single_quotes: bool, quiet: bool, verbose: bool, include: str, exclude: str, src: Tuple[str], config: Optional[str], ) -> None: """The uncompromising code formatter.""" write_back = WriteBack.from_configuration(check=check, diff=diff) if target_version: if py36: err('Cannot use both --target-version and --py36') ctx.exit(2) else: versions = set(target_version) elif py36: err('--py36 is deprecated and will be removed in a future version. ' 'Use --target-version py36 instead.') versions = PY36_VERSIONS else: # We'll autodetect later. versions = set() mode = FileMode( target_versions=versions, line_length=line_length, is_pyi=pyi, string_normalization=not skip_string_normalization, ) if single_quotes: black.normalize_string_quotes = patched_normalize_string_quotes if config and verbose: out(f'Using configuration from {config}.', bold=False, fg='blue') if code is not None: print(format_str(code, mode=mode)) ctx.exit(0) try: include_regex = re_compile_maybe_verbose(include) except re.error: err(f'Invalid regular expression for include given: {include!r}') ctx.exit(2) try: exclude_regex = re_compile_maybe_verbose(exclude) except re.error: err(f'Invalid regular expression for exclude given: {exclude!r}') ctx.exit(2) report = Report(check=check, quiet=quiet, verbose=verbose) root = find_project_root(src) sources: Set[Path] = set() path_empty(src=src, quiet=quiet, verbose=verbose, ctx=ctx, msg=None) for s in src: p = Path(s) if p.is_dir(): sources.update( gen_python_files_in_dir( p, root, include_regex, exclude_regex, report, get_gitignore(root), )) elif p.is_file() or s == '-': # if a file was explicitly given, we don't care about its extension sources.add(p) else: err(f'invalid path: {s}') if len(sources) == 0: if verbose or not quiet: out('No Python files are present to be formatted. Nothing to do 😴') ctx.exit(0) reformat_many( sources=sources, fast=fast, write_back=write_back, mode=mode, report=report, ) if verbose or not quiet: out('Oh no! 💥 💔 💥' if report.return_code else 'All done! ✨ 🍰 ✨') click.secho(str(report), err=True) ctx.exit(report.return_code)
def cli( ctx: click.Context, line_length: int, check: bool, include: str, exclude: str, quiet: bool, verbose: bool, clear_output: bool, src: Tuple[str], config: Optional[str], ) -> None: """ The uncompromising code formatter, for Jupyter notebooks. """ write_back = black.WriteBack.from_configuration(check=check, diff=False) mode = black.FileMode( target_versions=black.PY36_VERSIONS, line_length=line_length, is_pyi=False, string_normalization=True, ) if config and verbose: black.out(f"Using configuration from {config}.", bold=False, fg="blue") try: include_regex = black.re_compile_maybe_verbose(include) except re.error: black.err(f"Invalid regular expression for include given: {include!r}") ctx.exit(2) try: exclude_regex = black.re_compile_maybe_verbose(exclude) except re.error: black.err(f"Invalid regular expression for exclude given: {exclude!r}") ctx.exit(2) report = black.Report(check=check, quiet=quiet, verbose=verbose) root = black.find_project_root(src) sources: Set[Path] = set() for s in src: p = Path(s) if p.is_dir(): sources.update( black.gen_python_files_in_dir( p, root, include_regex, exclude_regex, report, black.get_gitignore(root), )) elif p.is_file() or s == "-": # if a file was explicitly given, we don't care about its extension sources.add(p) else: black.err(f"invalid path: {s}") if len(sources) == 0: if verbose or not quiet: black.out("No paths given. Nothing to do.") ctx.exit(0) for source in sources: reformat_one( src=source, write_back=write_back, mode=mode, clear_output=clear_output, report=report, quiet=quiet, verbose=verbose, ) if verbose or not quiet: black.out("All done!") click.secho(str(report), err=True) ctx.exit(report.return_code)