From 51756a405cd6006ef22e9c12f212905fe0907f80 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mika=E2=A0=99?= Date: Fri, 1 Jun 2018 02:51:15 +0200 Subject: [PATCH 01/16] Added --include and --exclude cli options (#281) These 2 options allow you to pass in regular expressions that determine whether files/directories are included or excluded in the recursive file search. Fixes #270 --- README.md | 13 +++ black.py | 84 ++++++++++++++----- .../b/.definitely_exclude/a.pie | 0 .../b/.definitely_exclude/a.py | 0 .../b/.definitely_exclude/a.pyi | 0 .../b/dont_exclude/a.pie | 0 .../include_exclude_tests/b/dont_exclude/a.py | 0 .../b/dont_exclude/a.pyi | 0 tests/include_exclude_tests/b/exclude/a.pie | 0 tests/include_exclude_tests/b/exclude/a.py | 0 tests/include_exclude_tests/b/exclude/a.pyi | 0 tests/test_black.py | 48 +++++++++++ 12 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 tests/include_exclude_tests/b/.definitely_exclude/a.pie create mode 100644 tests/include_exclude_tests/b/.definitely_exclude/a.py create mode 100644 tests/include_exclude_tests/b/.definitely_exclude/a.pyi create mode 100644 tests/include_exclude_tests/b/dont_exclude/a.pie create mode 100644 tests/include_exclude_tests/b/dont_exclude/a.py create mode 100644 tests/include_exclude_tests/b/dont_exclude/a.pyi create mode 100644 tests/include_exclude_tests/b/exclude/a.pie create mode 100644 tests/include_exclude_tests/b/exclude/a.py create mode 100644 tests/include_exclude_tests/b/exclude/a.pyi diff --git a/README.md b/README.md index 12af03f..79b8347 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,17 @@ Options: **kwargs. [default: per-file auto-detection] -S, --skip-string-normalization Don't normalize string quotes or prefixes. + --include TEXT A regular expression that matches files and + directories that should be included on + recursive searches. On Windows, use forward + slashes for directories. [default: \.pyi?$] + --exclude TEXT A regular expression that matches files and + directories that should be excluded on + recursive searches. On Windows, use forward + slashes for directories. [default: + build/|buck-out/|dist/|_build/|\.git/|\.hg/| + \.mypy_cache/|\.tox/|\.venv/] + --version Show the version and exit. --help Show this message and exit. ``` @@ -698,6 +709,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). ### 18.6b0 +* added `--include` and `--exclude` (#270) + * added `--skip-string-normalization` (#118) * fixed stdin handling not working correctly if an old version of Click was diff --git a/black.py b/black.py index 4599bdd..ce4a44f 100644 --- a/black.py +++ b/black.py @@ -46,6 +46,10 @@ from blib2to3.pgen2.parse import ParseError __version__ = "18.5b1" DEFAULT_LINE_LENGTH = 88 +DEFAULT_EXCLUDES = ( + r"build/|buck-out/|dist/|_build/|\.git/|\.hg/|\.mypy_cache/|\.tox/|\.venv/" +) +DEFAULT_INCLUDES = r"\.pyi?$" CACHE_DIR = Path(user_cache_dir("black", version=__version__)) @@ -189,6 +193,28 @@ class FileMode(Flag): is_flag=True, help="Don't normalize string quotes or prefixes.", ) +@click.option( + "--include", + type=str, + default=DEFAULT_INCLUDES, + help=( + "A regular expression that matches files and directories that should be " + "included on recursive searches. On Windows, use forward slashes for " + "directories." + ), + show_default=True, +) +@click.option( + "--exclude", + type=str, + default=DEFAULT_EXCLUDES, + help=( + "A regular expression that matches files and directories that should be " + "excluded on recursive searches. On Windows, use forward slashes for " + "directories." + ), + show_default=True, +) @click.version_option(version=__version__) @click.argument( "src", @@ -208,14 +234,26 @@ def main( py36: bool, skip_string_normalization: bool, quiet: bool, + include: str, + exclude: str, src: List[str], ) -> None: """The uncompromising code formatter.""" sources: List[Path] = [] + try: + include_regex = re.compile(include) + except re.error: + err(f"Invalid regular expression for include given: {include!r}") + ctx.exit(2) + try: + exclude_regex = re.compile(exclude) + except re.error: + err(f"Invalid regular expression for exclude given: {exclude!r}") + ctx.exit(2) for s in src: p = Path(s) if p.is_dir(): - sources.extend(gen_python_files_in_dir(p)) + sources.extend(gen_python_files_in_dir(p, include_regex, exclude_regex)) elif p.is_file(): # if a file was explicitly given, we don't care about its extension sources.append(p) @@ -2750,33 +2788,35 @@ def get_future_imports(node: Node) -> Set[str]: return imports -PYTHON_EXTENSIONS = {".py", ".pyi"} -BLACKLISTED_DIRECTORIES = { - "build", - "buck-out", - "dist", - "_build", - ".git", - ".hg", - ".mypy_cache", - ".tox", - ".venv", -} - - -def gen_python_files_in_dir(path: Path) -> Iterator[Path]: - """Generate all files under `path` which aren't under BLACKLISTED_DIRECTORIES - and have one of the PYTHON_EXTENSIONS. +def gen_python_files_in_dir( + path: Path, include: Pattern[str], exclude: Pattern[str] +) -> Iterator[Path]: + """Generate all files under `path` whose paths are not excluded by the + `exclude` regex, but are included by the `include` regex. """ + for child in path.iterdir(): + searchable_path = str(child.as_posix()) + if Path(child.parts[0]).is_dir(): + searchable_path = "/" + searchable_path if child.is_dir(): - if child.name in BLACKLISTED_DIRECTORIES: + searchable_path = searchable_path + "/" + exclude_match = exclude.search(searchable_path) + if exclude_match and len(exclude_match.group()) > 0: continue - yield from gen_python_files_in_dir(child) + yield from gen_python_files_in_dir(child, include, exclude) - elif child.is_file() and child.suffix in PYTHON_EXTENSIONS: - yield child + else: + include_match = include.search(searchable_path) + exclude_match = exclude.search(searchable_path) + if ( + child.is_file() + and include_match + and len(include_match.group()) > 0 + and (not exclude_match or len(exclude_match.group()) == 0) + ): + yield child @dataclass diff --git a/tests/include_exclude_tests/b/.definitely_exclude/a.pie b/tests/include_exclude_tests/b/.definitely_exclude/a.pie new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/.definitely_exclude/a.py b/tests/include_exclude_tests/b/.definitely_exclude/a.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/.definitely_exclude/a.pyi b/tests/include_exclude_tests/b/.definitely_exclude/a.pyi new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/dont_exclude/a.pie b/tests/include_exclude_tests/b/dont_exclude/a.pie new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/dont_exclude/a.py b/tests/include_exclude_tests/b/dont_exclude/a.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/dont_exclude/a.pyi b/tests/include_exclude_tests/b/dont_exclude/a.pyi new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/exclude/a.pie b/tests/include_exclude_tests/b/exclude/a.pie new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/exclude/a.py b/tests/include_exclude_tests/b/exclude/a.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/include_exclude_tests/b/exclude/a.pyi b/tests/include_exclude_tests/b/exclude/a.pyi new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_black.py b/tests/test_black.py index e654d0d..c10dd1d 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -11,6 +11,7 @@ from tempfile import TemporaryDirectory from typing import Any, List, Tuple, Iterator import unittest from unittest.mock import patch +import re from click import unstyle from click.testing import CliRunner @@ -851,6 +852,53 @@ class BlackTestCase(unittest.TestCase): actual = result.output self.assertFormatEqual(actual, expected) + def test_include_exclude(self) -> None: + path = THIS_DIR / "include_exclude_tests" + include = re.compile(r"\.pyi?$") + exclude = re.compile(r"/exclude/|/\.definitely_exclude/") + sources: List[Path] = [] + expected = [ + Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"), + Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"), + ] + sources.extend(black.gen_python_files_in_dir(path, include, exclude)) + self.assertEqual(sorted(expected), sorted(sources)) + + def test_empty_include(self) -> None: + path = THIS_DIR / "include_exclude_tests" + empty = re.compile(r"") + sources: List[Path] = [] + sources.extend( + black.gen_python_files_in_dir( + path, empty, re.compile(black.DEFAULT_EXCLUDES) + ) + ) + self.assertEqual([], (sources)) + + def test_empty_exclude(self) -> None: + path = THIS_DIR / "include_exclude_tests" + empty = re.compile(r"") + sources: List[Path] = [] + expected = [ + Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"), + Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"), + Path(THIS_DIR / "include_exclude_tests/b/exclude/a.py"), + Path(THIS_DIR / "include_exclude_tests/b/exclude/a.pyi"), + Path(THIS_DIR / "include_exclude_tests/b/.definitely_exclude/a.py"), + Path(THIS_DIR / "include_exclude_tests/b/.definitely_exclude/a.pyi"), + ] + sources.extend( + black.gen_python_files_in_dir( + path, re.compile(black.DEFAULT_INCLUDES), empty + ) + ) + self.assertEqual(sorted(expected), sorted(sources)) + + def test_invalid_include_exclude(self) -> None: + for option in ["--include", "--exclude"]: + result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"]) + self.assertEqual(result.exit_code, 2) + if __name__ == "__main__": unittest.main() -- 2.39.5 From 6fa60ba39d137bff08a7aa78beffa4773eb24121 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Thu, 31 May 2018 19:09:51 -0700 Subject: [PATCH 02/16] Sort default excludes, include the leading slash --- black.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/black.py b/black.py index ce4a44f..61b884a 100644 --- a/black.py +++ b/black.py @@ -47,7 +47,7 @@ from blib2to3.pgen2.parse import ParseError __version__ = "18.5b1" DEFAULT_LINE_LENGTH = 88 DEFAULT_EXCLUDES = ( - r"build/|buck-out/|dist/|_build/|\.git/|\.hg/|\.mypy_cache/|\.tox/|\.venv/" + r"/(\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist)/" ) DEFAULT_INCLUDES = r"\.pyi?$" CACHE_DIR = Path(user_cache_dir("black", version=__version__)) -- 2.39.5 From 435aa7ac4ab54d9bc30ee4d75859435c97493bb5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Thu, 31 May 2018 19:24:09 -0700 Subject: [PATCH 03/16] Reorder command-line options --- README.md | 25 ++++++++++++------------ black.py | 58 +++++++++++++++++++++++++++---------------------------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 79b8347..561b4eb 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,15 @@ black [OPTIONS] [SRC]... Options: -l, --line-length INTEGER Where to wrap around. [default: 88] + --py36 Allow using Python 3.6-only syntax on all input + files. This will put trailing commas in function + signatures and calls also after *args and + **kwargs. [default: per-file auto-detection] + --pyi Format all input files like typing stubs + regardless of file extension (useful when piping + source on standard input). + -S, --skip-string-normalization + Don't normalize string quotes or prefixes. --check Don't write the files back, just return the status. Return code 0 means nothing would change. Return code 1 means some files would be @@ -77,18 +86,6 @@ Options: for each file on stdout. --fast / --safe If --fast given, skip temporary sanity checks. [default: --safe] - -q, --quiet Don't emit non-error messages to stderr. Errors - are still emitted, silence those with - 2>/dev/null. - --pyi Consider all input files typing stubs regardless - of file extension (useful when piping source on - standard input). - --py36 Allow using Python 3.6-only syntax on all input - files. This will put trailing commas in function - signatures and calls also after *args and - **kwargs. [default: per-file auto-detection] - -S, --skip-string-normalization - Don't normalize string quotes or prefixes. --include TEXT A regular expression that matches files and directories that should be included on recursive searches. On Windows, use forward @@ -99,7 +96,9 @@ Options: slashes for directories. [default: build/|buck-out/|dist/|_build/|\.git/|\.hg/| \.mypy_cache/|\.tox/|\.venv/] - + -q, --quiet Don't emit non-error messages to stderr. Errors + are still emitted, silence those with + 2>/dev/null. --version Show the version and exit. --help Show this message and exit. ``` diff --git a/black.py b/black.py index 61b884a..7587ba8 100644 --- a/black.py +++ b/black.py @@ -143,55 +143,46 @@ class FileMode(Flag): show_default=True, ) @click.option( - "--check", + "--py36", is_flag=True, help=( - "Don't write the files back, just return the status. Return code 0 " - "means nothing would change. Return code 1 means some files would be " - "reformatted. Return code 123 means there was an internal error." + "Allow using Python 3.6-only syntax on all input files. This will put " + "trailing commas in function signatures and calls also after *args and " + "**kwargs. [default: per-file auto-detection]" ), ) @click.option( - "--diff", - is_flag=True, - help="Don't write the files back, just output a diff for each file on stdout.", -) -@click.option( - "--fast/--safe", + "--pyi", is_flag=True, - help="If --fast given, skip temporary sanity checks. [default: --safe]", + help=( + "Format all input files like typing stubs regardless of file extension " + "(useful when piping source on standard input)." + ), ) @click.option( - "-q", - "--quiet", + "-S", + "--skip-string-normalization", is_flag=True, - help=( - "Don't emit non-error messages to stderr. Errors are still emitted, " - "silence those with 2>/dev/null." - ), + help="Don't normalize string quotes or prefixes.", ) @click.option( - "--pyi", + "--check", is_flag=True, help=( - "Consider all input files typing stubs regardless of file extension " - "(useful when piping source on standard input)." + "Don't write the files back, just return the status. Return code 0 " + "means nothing would change. Return code 1 means some files would be " + "reformatted. Return code 123 means there was an internal error." ), ) @click.option( - "--py36", + "--diff", is_flag=True, - help=( - "Allow using Python 3.6-only syntax on all input files. This will put " - "trailing commas in function signatures and calls also after *args and " - "**kwargs. [default: per-file auto-detection]" - ), + help="Don't write the files back, just output a diff for each file on stdout.", ) @click.option( - "-S", - "--skip-string-normalization", + "--fast/--safe", is_flag=True, - help="Don't normalize string quotes or prefixes.", + help="If --fast given, skip temporary sanity checks. [default: --safe]", ) @click.option( "--include", @@ -215,6 +206,15 @@ class FileMode(Flag): ), show_default=True, ) +@click.option( + "-q", + "--quiet", + is_flag=True, + help=( + "Don't emit non-error messages to stderr. Errors are still emitted, " + "silence those with 2>/dev/null." + ), +) @click.version_option(version=__version__) @click.argument( "src", -- 2.39.5 From 8a82e0bf9af1182563abb8bfb0c4e87e836feb95 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Thu, 31 May 2018 19:40:07 -0700 Subject: [PATCH 04/16] Make empty --include mean "anything goes", simplify `gen_python_files_in_dir` --- black.py | 37 ++++++++++++++++--------------------- tests/test_black.py | 25 ++++++++++++++++++------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/black.py b/black.py index 7587ba8..8ac6d87 100644 --- a/black.py +++ b/black.py @@ -190,8 +190,10 @@ class FileMode(Flag): default=DEFAULT_INCLUDES, help=( "A regular expression that matches files and directories that should be " - "included on recursive searches. On Windows, use forward slashes for " - "directories." + "included on recursive searches. An empty value means all files are " + "included regardless of the name. Use forward slashes for directories on " + "all platforms (Windows, too). Exclusions are calculated first, inclusions " + "later." ), show_default=True, ) @@ -201,8 +203,9 @@ class FileMode(Flag): default=DEFAULT_EXCLUDES, help=( "A regular expression that matches files and directories that should be " - "excluded on recursive searches. On Windows, use forward slashes for " - "directories." + "excluded on recursive searches. An empty value means no paths are excluded. " + "Use forward slashes for directories on all platforms (Windows, too). " + "Exclusions are calculated first, inclusions later." ), show_default=True, ) @@ -2794,28 +2797,20 @@ def gen_python_files_in_dir( """Generate all files under `path` whose paths are not excluded by the `exclude` regex, but are included by the `include` regex. """ - for child in path.iterdir(): - searchable_path = str(child.as_posix()) - if Path(child.parts[0]).is_dir(): - searchable_path = "/" + searchable_path + normalized_path = child.resolve().as_posix() if child.is_dir(): - searchable_path = searchable_path + "/" - exclude_match = exclude.search(searchable_path) - if exclude_match and len(exclude_match.group()) > 0: - continue + normalized_path += "/" + exclude_match = exclude.search(normalized_path) + if exclude_match and exclude_match.group(0): + continue + if child.is_dir(): yield from gen_python_files_in_dir(child, include, exclude) - else: - include_match = include.search(searchable_path) - exclude_match = exclude.search(searchable_path) - if ( - child.is_file() - and include_match - and len(include_match.group()) > 0 - and (not exclude_match or len(exclude_match.group()) == 0) - ): + elif child.is_file(): + include_match = include.search(normalized_path) + if include_match: yield child diff --git a/tests/test_black.py b/tests/test_black.py index c10dd1d..08a3f31 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -868,24 +868,35 @@ class BlackTestCase(unittest.TestCase): path = THIS_DIR / "include_exclude_tests" empty = re.compile(r"") sources: List[Path] = [] + expected = [ + Path(path / "b/exclude/a.pie"), + Path(path / "b/exclude/a.py"), + Path(path / "b/exclude/a.pyi"), + Path(path / "b/dont_exclude/a.pie"), + Path(path / "b/dont_exclude/a.py"), + Path(path / "b/dont_exclude/a.pyi"), + Path(path / "b/.definitely_exclude/a.pie"), + Path(path / "b/.definitely_exclude/a.py"), + Path(path / "b/.definitely_exclude/a.pyi"), + ] sources.extend( black.gen_python_files_in_dir( path, empty, re.compile(black.DEFAULT_EXCLUDES) ) ) - self.assertEqual([], (sources)) + self.assertEqual(sorted(expected), sorted(sources)) def test_empty_exclude(self) -> None: path = THIS_DIR / "include_exclude_tests" empty = re.compile(r"") sources: List[Path] = [] expected = [ - Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"), - Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"), - Path(THIS_DIR / "include_exclude_tests/b/exclude/a.py"), - Path(THIS_DIR / "include_exclude_tests/b/exclude/a.pyi"), - Path(THIS_DIR / "include_exclude_tests/b/.definitely_exclude/a.py"), - Path(THIS_DIR / "include_exclude_tests/b/.definitely_exclude/a.pyi"), + Path(path / "b/dont_exclude/a.py"), + Path(path / "b/dont_exclude/a.pyi"), + Path(path / "b/exclude/a.py"), + Path(path / "b/exclude/a.pyi"), + Path(path / "b/.definitely_exclude/a.py"), + Path(path / "b/.definitely_exclude/a.pyi"), ] sources.extend( black.gen_python_files_in_dir( -- 2.39.5 From a80e037a9ad5867371d8df0cc8cc9c7520ccc020 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Thu, 31 May 2018 19:47:24 -0700 Subject: [PATCH 05/16] Don't over-eagerly make a path absolute if only one passed If a directory or more than one file is passed, Black nicely shows the relative paths in output. Before this change, it showed an absolute path if only a single file was passed as an argument. This fixes the inconsistency. --- black.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/black.py b/black.py index 8ac6d87..da00525 100644 --- a/black.py +++ b/black.py @@ -342,8 +342,8 @@ def reformat_one( cache: Cache = {} if write_back != WriteBack.DIFF: cache = read_cache(line_length, mode) - src = src.resolve() - if src in cache and cache[src] == get_cache_info(src): + res_src = src.resolve() + if res_src in cache and cache[res_src] == get_cache_info(res_src): changed = Changed.CACHED if changed is not Changed.CACHED and format_file_in_place( src, -- 2.39.5 From ef903ecd4688dbc9fc1166caf837420aa2ffdbac Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Fri, 1 Jun 2018 23:47:07 +0200 Subject: [PATCH 06/16] Skip symlink test if can't create one (#287) --- tests/test_black.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_black.py b/tests/test_black.py index 08a3f31..7e50c2f 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -731,7 +731,10 @@ class BlackTestCase(unittest.TestCase): def test_broken_symlink(self) -> None: with cache_dir() as workspace: symlink = workspace / "broken_link.py" - symlink.symlink_to("nonexistent.py") + try: + symlink.symlink_to("nonexistent.py") + except OSError as e: + self.skipTest(f"Can't create symlinks: {e}") result = CliRunner().invoke(black.main, [str(workspace.resolve())]) self.assertEqual(result.exit_code, 0) -- 2.39.5 From 1687892d63fdff7525bb50a0166db3c5214ce2de Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Fri, 1 Jun 2018 23:12:20 +0100 Subject: [PATCH 07/16] don't run tests from /build --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 77cdbc4..1b95686 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,9 @@ install: - pip install coverage coveralls flake8 flake8-bugbear mypy - pip install -e . script: -- coverage run tests/test_black.py +- sudo mkdir -p /black +- sudo mount --bind $(pwd) /black +- coverage run /black/tests/test_black.py - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then mypy black.py tests/test_black.py; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.6-dev' ]]; then flake8 black.py tests/test_black.py; fi after_success: -- 2.39.5 From ecb3d8c472a369bdf04ec22adad3ef4793183bc1 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 3 Jun 2018 11:46:59 -0700 Subject: [PATCH 08/16] `python_version` => `language_version` (#296) Noticed this in `pytest`'s config -- `python_version` isn't a thing :D --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 561b4eb..8a70883 100644 --- a/README.md +++ b/README.md @@ -627,13 +627,13 @@ repos: hooks: - id: black args: [--line-length=88, --safe] - python_version: python3.6 + language_version: python3.6 ``` Then run `pre-commit install` and you're ready to go. `args` in the above config is optional but shows you how you can change the line length if you really need to. If you're already using Python -3.7, switch the `python_version` accordingly. Finally, `stable` is a tag +3.7, switch the `language_version` accordingly. Finally, `stable` is a tag that is pinned to the latest release on PyPI. If you'd rather run on master, this is also an option. -- 2.39.5 From 8b8795436170de38dfacc05a947b2a2a1e42c33f Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Fri, 1 Jun 2018 14:14:22 -0700 Subject: [PATCH 09/16] Add .pie from tests to MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index cc876dd..a92dd68 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include *.rst *.md LICENSE recursive-include blib2to3 *.txt *.py LICENSE -recursive-include tests *.txt *.out *.diff *.py *.pyi +recursive-include tests *.txt *.out *.diff *.py *.pyi *.pie -- 2.39.5 From 36bed855e11b119adc4cd5b3ad87e2da965928ba Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 10:59:36 -0700 Subject: [PATCH 10/16] Introduce "project root" as a concept This is required for regular expressions in `--include=` and `--exclude=` not to catch false positives from directories outside of the project. --- black.py | 41 +++++++++++++++++++++++++++++++++++++---- tests/test_black.py | 9 ++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/black.py b/black.py index da00525..730c64d 100644 --- a/black.py +++ b/black.py @@ -253,10 +253,13 @@ def main( except re.error: err(f"Invalid regular expression for exclude given: {exclude!r}") ctx.exit(2) + root = find_project_root(src) for s in src: p = Path(s) if p.is_dir(): - sources.extend(gen_python_files_in_dir(p, include_regex, exclude_regex)) + sources.extend( + gen_python_files_in_dir(p, root, include_regex, exclude_regex) + ) elif p.is_file(): # if a file was explicitly given, we don't care about its extension sources.append(p) @@ -2792,13 +2795,14 @@ def get_future_imports(node: Node) -> Set[str]: def gen_python_files_in_dir( - path: Path, include: Pattern[str], exclude: Pattern[str] + path: Path, root: Path, include: Pattern[str], exclude: Pattern[str] ) -> Iterator[Path]: """Generate all files under `path` whose paths are not excluded by the `exclude` regex, but are included by the `include` regex. """ + assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}" for child in path.iterdir(): - normalized_path = child.resolve().as_posix() + normalized_path = child.resolve().relative_to(root).as_posix() if child.is_dir(): normalized_path += "/" exclude_match = exclude.search(normalized_path) @@ -2806,7 +2810,7 @@ def gen_python_files_in_dir( continue if child.is_dir(): - yield from gen_python_files_in_dir(child, include, exclude) + yield from gen_python_files_in_dir(child, root, include, exclude) elif child.is_file(): include_match = include.search(normalized_path) @@ -2814,6 +2818,35 @@ def gen_python_files_in_dir( yield child +def find_project_root(srcs: List[str]) -> Path: + """Return a directory containing .git, .hg, or pyproject.toml. + + That directory can be one of the directories passed in `srcs` or their + common parent. + + If no directory in the tree contains a marker that would specify it's the + project root, the root of the file system is returned. + """ + if not srcs: + return Path("/").resolve() + + common_base = min(Path(src).resolve() for src in srcs) + if common_base.is_dir(): + # Append a fake file so `parents` below returns `common_base_dir`, too. + common_base /= "fake-file" + for directory in common_base.parents: + if (directory / ".git").is_dir(): + return directory + + if (directory / ".hg").is_dir(): + return directory + + if (directory / "pyproject.toml").is_file(): + return directory + + return directory + + @dataclass class Report: """Provides a reformatting counter. Can be rendered with `str(report)`.""" diff --git a/tests/test_black.py b/tests/test_black.py index 7e50c2f..7389da9 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -864,7 +864,8 @@ class BlackTestCase(unittest.TestCase): Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"), Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"), ] - sources.extend(black.gen_python_files_in_dir(path, include, exclude)) + this_abs = THIS_DIR.resolve() + sources.extend(black.gen_python_files_in_dir(path, this_abs, include, exclude)) self.assertEqual(sorted(expected), sorted(sources)) def test_empty_include(self) -> None: @@ -882,9 +883,10 @@ class BlackTestCase(unittest.TestCase): Path(path / "b/.definitely_exclude/a.py"), Path(path / "b/.definitely_exclude/a.pyi"), ] + this_abs = THIS_DIR.resolve() sources.extend( black.gen_python_files_in_dir( - path, empty, re.compile(black.DEFAULT_EXCLUDES) + path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES) ) ) self.assertEqual(sorted(expected), sorted(sources)) @@ -901,9 +903,10 @@ class BlackTestCase(unittest.TestCase): Path(path / "b/.definitely_exclude/a.py"), Path(path / "b/.definitely_exclude/a.pyi"), ] + this_abs = THIS_DIR.resolve() sources.extend( black.gen_python_files_in_dir( - path, re.compile(black.DEFAULT_INCLUDES), empty + path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty ) ) self.assertEqual(sorted(expected), sorted(sources)) -- 2.39.5 From 4cb338e3dd1fd0a1b1725c764ed21d5aeeb0ac69 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 11:03:45 -0700 Subject: [PATCH 11/16] Revert "don't run tests from /build" This reverts commit 1687892d63fdff7525bb50a0166db3c5214ce2de. This is no longer necessary with the fix in the previous commit. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b95686..77cdbc4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,7 @@ install: - pip install coverage coveralls flake8 flake8-bugbear mypy - pip install -e . script: -- sudo mkdir -p /black -- sudo mount --bind $(pwd) /black -- coverage run /black/tests/test_black.py +- coverage run tests/test_black.py - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then mypy black.py tests/test_black.py; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.6-dev' ]]; then flake8 black.py tests/test_black.py; fi after_success: -- 2.39.5 From 1aa14c5db05681a2c25b2c3757d3f8d8f3bbe85f Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 11:06:38 -0700 Subject: [PATCH 12/16] [trivial] Simplify stdin handling --- black.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/black.py b/black.py index 730c64d..0dce397 100644 --- a/black.py +++ b/black.py @@ -260,11 +260,9 @@ def main( sources.extend( gen_python_files_in_dir(p, root, include_regex, exclude_regex) ) - elif p.is_file(): + elif p.is_file() or s == "-": # if a file was explicitly given, we don't care about its extension sources.append(p) - elif s == "-": - sources.append(Path("-")) else: err(f"invalid path: {s}") -- 2.39.5 From e7b312fb434483e0332643ae1e9736257f4f60c1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 11:20:15 -0700 Subject: [PATCH 13/16] [trivial] Simplify `mode` and `write_back` calculation in main() --- black.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/black.py b/black.py index 0dce397..2ccb898 100644 --- a/black.py +++ b/black.py @@ -119,6 +119,13 @@ class WriteBack(Enum): YES = 1 DIFF = 2 + @classmethod + def from_configuration(cls, *, check: bool, diff: bool) -> "WriteBack": + if check and not diff: + return cls.NO + + return cls.DIFF if diff else cls.YES + class Changed(Enum): NO = 0 @@ -132,6 +139,19 @@ class FileMode(Flag): PYI = 2 NO_STRING_NORMALIZATION = 4 + @classmethod + def from_configuration( + cls, *, py36: bool, pyi: bool, skip_string_normalization: bool + ) -> "FileMode": + mode = cls.AUTO_DETECT + if py36: + mode |= cls.PYTHON36 + if pyi: + mode |= cls.PYI + if skip_string_normalization: + mode |= cls.NO_STRING_NORMALIZATION + return mode + @click.command() @click.option( @@ -242,6 +262,11 @@ def main( src: List[str], ) -> None: """The uncompromising code formatter.""" + write_back = WriteBack.from_configuration(check=check, diff=diff) + mode = FileMode.from_configuration( + py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization + ) + report = Report(check=check, quiet=quiet) sources: List[Path] = [] try: include_regex = re.compile(include) @@ -265,21 +290,6 @@ def main( sources.append(p) else: err(f"invalid path: {s}") - - if check and not diff: - write_back = WriteBack.NO - elif diff: - write_back = WriteBack.DIFF - else: - write_back = WriteBack.YES - mode = FileMode.AUTO_DETECT - if py36: - mode |= FileMode.PYTHON36 - if pyi: - mode |= FileMode.PYI - if skip_string_normalization: - mode |= FileMode.NO_STRING_NORMALIZATION - report = Report(check=check, quiet=quiet) if len(sources) == 0: out("No paths given. Nothing to do 😴") ctx.exit(0) -- 2.39.5 From 4c352ad4be70c72ba9b949d3afb7c242522d058e Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 11:58:26 -0700 Subject: [PATCH 14/16] Add `--verbose` and report excluded paths in it, too Fixes #283 --- README.md | 5 ++ black.py | 34 ++++++-- tests/test_black.py | 204 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 231 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8a70883..b1eae74 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,9 @@ Options: -q, --quiet Don't emit non-error messages to stderr. Errors are still emitted, silence those with 2>/dev/null. + -v, --verbose Also emit messages to stderr about files + that were not changed or were ignored due to + --exclude=. --version Show the version and exit. --help Show this message and exit. ``` @@ -712,6 +715,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * added `--skip-string-normalization` (#118) +* added `--verbose` (#283) + * fixed stdin handling not working correctly if an old version of Click was used (#276) diff --git a/black.py b/black.py index 2ccb898..b3b37ef 100644 --- a/black.py +++ b/black.py @@ -238,6 +238,15 @@ class FileMode(Flag): "silence those with 2>/dev/null." ), ) +@click.option( + "-v", + "--verbose", + is_flag=True, + help=( + "Also emit messages to stderr about files that were not changed or were " + "ignored due to --exclude=." + ), +) @click.version_option(version=__version__) @click.argument( "src", @@ -257,6 +266,7 @@ def main( py36: bool, skip_string_normalization: bool, quiet: bool, + verbose: bool, include: str, exclude: str, src: List[str], @@ -266,7 +276,7 @@ def main( mode = FileMode.from_configuration( py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization ) - report = Report(check=check, quiet=quiet) + report = Report(check=check, quiet=quiet, verbose=verbose) sources: List[Path] = [] try: include_regex = re.compile(include) @@ -283,7 +293,7 @@ def main( p = Path(s) if p.is_dir(): sources.extend( - gen_python_files_in_dir(p, root, include_regex, exclude_regex) + gen_python_files_in_dir(p, root, include_regex, exclude_regex, report) ) elif p.is_file() or s == "-": # if a file was explicitly given, we don't care about its extension @@ -2803,10 +2813,16 @@ def get_future_imports(node: Node) -> Set[str]: def gen_python_files_in_dir( - path: Path, root: Path, include: Pattern[str], exclude: Pattern[str] + path: Path, + root: Path, + include: Pattern[str], + exclude: Pattern[str], + report: "Report", ) -> Iterator[Path]: """Generate all files under `path` whose paths are not excluded by the `exclude` regex, but are included by the `include` regex. + + `report` is where output about exclusions goes. """ assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}" for child in path.iterdir(): @@ -2815,10 +2831,11 @@ def gen_python_files_in_dir( normalized_path += "/" exclude_match = exclude.search(normalized_path) if exclude_match and exclude_match.group(0): + report.path_ignored(child, f"matches --exclude={exclude.pattern}") continue if child.is_dir(): - yield from gen_python_files_in_dir(child, root, include, exclude) + yield from gen_python_files_in_dir(child, root, include, exclude, report) elif child.is_file(): include_match = include.search(normalized_path) @@ -2861,6 +2878,7 @@ class Report: check: bool = False quiet: bool = False + verbose: bool = False change_count: int = 0 same_count: int = 0 failure_count: int = 0 @@ -2869,11 +2887,11 @@ class Report: """Increment the counter for successful reformatting. Write out a message.""" if changed is Changed.YES: reformatted = "would reformat" if self.check else "reformatted" - if not self.quiet: + if self.verbose or not self.quiet: out(f"{reformatted} {src}") self.change_count += 1 else: - if not self.quiet: + if self.verbose: if changed is Changed.NO: msg = f"{src} already well formatted, good job." else: @@ -2886,6 +2904,10 @@ class Report: err(f"error: cannot format {src}: {message}") self.failure_count += 1 + def path_ignored(self, path: Path, message: str) -> None: + if self.verbose: + out(f"{path} ignored: {message}", bold=False) + @property def return_code(self) -> int: """Return the exit code that the app should use. diff --git a/tests/test_black.py b/tests/test_black.py index 7389da9..adf5ede 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -378,8 +378,8 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) - def test_report(self) -> None: - report = black.Report() + def test_report_verbose(self) -> None: + report = black.Report(verbose=True) out_lines = [] err_lines = [] @@ -446,9 +446,19 @@ class BlackTestCase(unittest.TestCase): "2 files failed to reformat.", ) self.assertEqual(report.return_code, 123) - report.done(Path("f4"), black.Changed.NO) + report.path_ignored(Path("wat"), "no match") self.assertEqual(len(out_lines), 5) self.assertEqual(len(err_lines), 2) + self.assertEqual(out_lines[-1], "wat ignored: no match") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 6) + self.assertEqual(len(err_lines), 2) self.assertEqual(out_lines[-1], "f4 already well formatted, good job.") self.assertEqual( unstyle(str(report)), @@ -463,6 +473,183 @@ class BlackTestCase(unittest.TestCase): "2 files would fail to reformat.", ) + def test_report_quiet(self) -> None: + report = black.Report(quiet=True) + out_lines = [] + err_lines = [] + + def out(msg: str, **kwargs: Any) -> None: + out_lines.append(msg) + + def err(msg: str, **kwargs: Any) -> None: + err_lines.append(msg) + + with patch("black.out", out), patch("black.err", err): + report.done(Path("f1"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual(unstyle(str(report)), "1 file left unchanged.") + self.assertEqual(report.return_code, 0) + report.done(Path("f2"), black.Changed.YES) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file left unchanged." + ) + report.done(Path("f3"), black.Changed.CACHED) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 2 files left unchanged." + ) + self.assertEqual(report.return_code, 0) + report.check = True + self.assertEqual(report.return_code, 1) + report.check = False + report.failed(Path("e1"), "boom") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 1) + self.assertEqual(err_lines[-1], "error: cannot format e1: boom") + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f3"), black.Changed.YES) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 1) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.failed(Path("e2"), "boom") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual(err_lines[-1], "error: cannot format e2: boom") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.path_ignored(Path("wat"), "no match") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 3 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.check = True + self.assertEqual( + unstyle(str(report)), + "2 files would be reformatted, 3 files would be left unchanged, " + "2 files would fail to reformat.", + ) + + def test_report_normal(self) -> None: + report = black.Report() + out_lines = [] + err_lines = [] + + def out(msg: str, **kwargs: Any) -> None: + out_lines.append(msg) + + def err(msg: str, **kwargs: Any) -> None: + err_lines.append(msg) + + with patch("black.out", out), patch("black.err", err): + report.done(Path("f1"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual(unstyle(str(report)), "1 file left unchanged.") + self.assertEqual(report.return_code, 0) + report.done(Path("f2"), black.Changed.YES) + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 0) + self.assertEqual(out_lines[-1], "reformatted f2") + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file left unchanged." + ) + report.done(Path("f3"), black.Changed.CACHED) + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 0) + self.assertEqual(out_lines[-1], "reformatted f2") + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 2 files left unchanged." + ) + self.assertEqual(report.return_code, 0) + report.check = True + self.assertEqual(report.return_code, 1) + report.check = False + report.failed(Path("e1"), "boom") + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 1) + self.assertEqual(err_lines[-1], "error: cannot format e1: boom") + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f3"), black.Changed.YES) + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 1) + self.assertEqual(out_lines[-1], "reformatted f3") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.failed(Path("e2"), "boom") + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual(err_lines[-1], "error: cannot format e2: boom") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.path_ignored(Path("wat"), "no match") + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 3 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.check = True + self.assertEqual( + unstyle(str(report)), + "2 files would be reformatted, 3 files would be left unchanged, " + "2 files would fail to reformat.", + ) + def test_is_python36(self) -> None: node = black.lib2to3_parse("def f(*, arg): ...\n") self.assertFalse(black.is_python36(node)) @@ -859,17 +1046,21 @@ class BlackTestCase(unittest.TestCase): path = THIS_DIR / "include_exclude_tests" include = re.compile(r"\.pyi?$") exclude = re.compile(r"/exclude/|/\.definitely_exclude/") + report = black.Report() sources: List[Path] = [] expected = [ Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"), Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"), ] this_abs = THIS_DIR.resolve() - sources.extend(black.gen_python_files_in_dir(path, this_abs, include, exclude)) + sources.extend( + black.gen_python_files_in_dir(path, this_abs, include, exclude, report) + ) self.assertEqual(sorted(expected), sorted(sources)) def test_empty_include(self) -> None: path = THIS_DIR / "include_exclude_tests" + report = black.Report() empty = re.compile(r"") sources: List[Path] = [] expected = [ @@ -886,13 +1077,14 @@ class BlackTestCase(unittest.TestCase): this_abs = THIS_DIR.resolve() sources.extend( black.gen_python_files_in_dir( - path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES) + path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report ) ) self.assertEqual(sorted(expected), sorted(sources)) def test_empty_exclude(self) -> None: path = THIS_DIR / "include_exclude_tests" + report = black.Report() empty = re.compile(r"") sources: List[Path] = [] expected = [ @@ -906,7 +1098,7 @@ class BlackTestCase(unittest.TestCase): this_abs = THIS_DIR.resolve() sources.extend( black.gen_python_files_in_dir( - path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty + path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report ) ) self.assertEqual(sorted(expected), sorted(sources)) -- 2.39.5 From 75d2af2e3a0d0cf85b1e0c510c626d1ee1938074 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 12:18:27 -0700 Subject: [PATCH 15/16] Fix missing leading slash due to `relative_to()` resolution --- black.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/black.py b/black.py index b3b37ef..e59a1e5 100644 --- a/black.py +++ b/black.py @@ -2826,7 +2826,7 @@ def gen_python_files_in_dir( """ assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}" for child in path.iterdir(): - normalized_path = child.resolve().relative_to(root).as_posix() + normalized_path = "/" + child.resolve().relative_to(root).as_posix() if child.is_dir(): normalized_path += "/" exclude_match = exclude.search(normalized_path) -- 2.39.5 From dbe26161fa68632d608a440666a0960a32630902 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 12:24:20 -0700 Subject: [PATCH 16/16] Reformat docs/conf.py, too. --- .pre-commit-config.yaml | 2 +- .travis.yml | 1 + docs/conf.py | 139 ++++++++++++++++++++-------------------- 3 files changed, 71 insertions(+), 71 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b87604a..99fb823 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ name: black language: system entry: python3 -m black - files: ^(black|setup|tests/test_black)\.py$ + files: ^(black|setup|tests/test_black|docs/conf)\.py$ - id: flake8 name: flake8 language: system diff --git a/.travis.yml b/.travis.yml index 77cdbc4..c8507ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ script: - coverage run tests/test_black.py - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then mypy black.py tests/test_black.py; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.6-dev' ]]; then flake8 black.py tests/test_black.py; fi +- if [[ $TRAVIS_PYTHON_VERSION == '3.7-dev' ]]; then black --check --verbose black.py setup.py tests/test_black.py docs/conf.py; fi after_success: - coveralls notifications: diff --git a/docs/conf.py b/docs/conf.py index 9599afd..aeb57f9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,44 +25,44 @@ CURRENT_DIR = Path(__file__).parent def get_version(): - black_py = CURRENT_DIR / '..' / 'black.py' - _version_re = re.compile(r'__version__\s+=\s+(?P.*)') - with open(str(black_py), 'r', encoding='utf8') as f: - version = _version_re.search(f.read()).group('version') + black_py = CURRENT_DIR / ".." / "black.py" + _version_re = re.compile(r"__version__\s+=\s+(?P.*)") + with open(str(black_py), "r", encoding="utf8") as f: + version = _version_re.search(f.read()).group("version") return str(ast.literal_eval(version)) def make_pypi_svg(version): - template = CURRENT_DIR / '_static' / 'pypi_template.svg' - target = CURRENT_DIR / '_static' / 'pypi.svg' - with open(str(template), 'r', encoding='utf8') as f: + template = CURRENT_DIR / "_static" / "pypi_template.svg" + target = CURRENT_DIR / "_static" / "pypi.svg" + with open(str(template), "r", encoding="utf8") as f: svg = string.Template(f.read()).substitute(version=version) - with open(str(target), 'w', encoding='utf8') as f: + with open(str(target), "w", encoding="utf8") as f: f.write(svg) def make_filename(line): - non_letters = re.compile(r'[^a-z]+') + non_letters = re.compile(r"[^a-z]+") filename = line[3:].rstrip().lower() - filename = non_letters.sub('_', filename) - if filename.startswith('_'): + filename = non_letters.sub("_", filename) + if filename.startswith("_"): filename = filename[1:] - if filename.endswith('_'): + if filename.endswith("_"): filename = filename[:-1] - return filename + '.md' + return filename + ".md" def generate_sections_from_readme(): - target_dir = CURRENT_DIR / '_build' / 'generated' - readme = CURRENT_DIR / '..' / 'README.md' + target_dir = CURRENT_DIR / "_build" / "generated" + readme = CURRENT_DIR / ".." / "README.md" shutil.rmtree(str(target_dir), ignore_errors=True) target_dir.mkdir(parents=True) output = None target_dir = target_dir.relative_to(CURRENT_DIR) - with open(str(readme), 'r', encoding='utf8') as f: + with open(str(readme), "r", encoding="utf8") as f: for line in f: - if line.startswith('## '): + if line.startswith("## "): if output is not None: output.close() filename = make_filename(line) @@ -70,15 +70,15 @@ def generate_sections_from_readme(): if output_path.is_symlink() or output_path.is_file(): output_path.unlink() output_path.symlink_to(target_dir / filename) - output = open(str(output_path), 'w', encoding='utf8') + output = open(str(output_path), "w", encoding="utf8") output.write( - '[//]: # (NOTE: THIS FILE IS AUTOGENERATED FROM README.md)\n\n' + "[//]: # (NOTE: THIS FILE IS AUTOGENERATED FROM README.md)\n\n" ) if output is None: continue - if line.startswith('##'): + if line.startswith("##"): line = line[1:] output.write(line) @@ -86,16 +86,16 @@ def generate_sections_from_readme(): # -- Project information ----------------------------------------------------- -project = 'Black' -copyright = '2018, Łukasz Langa and contributors to Black' -author = 'Łukasz Langa and contributors to Black' +project = "Black" +copyright = "2018, Łukasz Langa and contributors to Black" +author = "Łukasz Langa and contributors to Black" # Autopopulate version # The full version, including alpha/beta/rc tags. release = get_version() # The short X.Y version. version = release -for sp in 'abcfr': +for sp in "abcfr": version = version.split(sp)[0] make_pypi_svg(release) generate_sections_from_readme() @@ -110,25 +110,19 @@ generate_sections_from_readme() # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', -] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.napoleon"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] -source_parsers = { - '.md': CommonMarkParser, -} +source_parsers = {".md": CommonMarkParser} # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -140,10 +134,10 @@ language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # -- Options for HTML output ------------------------------------------------- @@ -151,35 +145,35 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', - 'sourcelink.html', - 'searchbox.html' + "**": [ + "about.html", + "navigation.html", + "relations.html", + "sourcelink.html", + "searchbox.html", ] } html_theme_options = { - 'show_related': False, - 'description': '“Any color you like.”', - 'github_button': True, - 'github_user': 'ambv', - 'github_repo': 'black', - 'github_type': 'star', - 'show_powered_by': True, - 'fixed_sidebar': True, - 'logo': 'logo2.png', + "show_related": False, + "description": "“Any color you like.”", + "github_button": True, + "github_user": "ambv", + "github_repo": "black", + "github_type": "star", + "show_powered_by": True, + "fixed_sidebar": True, + "logo": "logo2.png", } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -195,7 +189,7 @@ html_static_path = ['_static'] # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'blackdoc' +htmlhelp_basename = "blackdoc" # -- Options for LaTeX output ------------------------------------------------ @@ -204,15 +198,12 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -222,8 +213,13 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'black.tex', 'Documentation for Black', - 'Łukasz Langa and contributors to Black', 'manual'), + ( + master_doc, + "black.tex", + "Documentation for Black", + "Łukasz Langa and contributors to Black", + "manual", + ) ] @@ -231,10 +227,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'black', 'Documentation for Black', - [author], 1) -] +man_pages = [(master_doc, "black", "Documentation for Black", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -243,9 +236,15 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Black', 'Documentation for Black', - author, 'Black', 'The uncompromising Python code formatter', - 'Miscellaneous'), + ( + master_doc, + "Black", + "Documentation for Black", + author, + "Black", + "The uncompromising Python code formatter", + "Miscellaneous", + ) ] @@ -267,14 +266,14 @@ epub_copyright = copyright # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- -autodoc_member_order = 'bysource' +autodoc_member_order = "bysource" # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/3/': None} +intersphinx_mapping = {"https://docs.python.org/3/": None} -- 2.39.5