diff --git a/.github/workflows/diff_shades_comment.yml b/.github/workflows/diff_shades_comment.yml index a5d213875c7..9a22ec5f0c3 100644 --- a/.github/workflows/diff_shades_comment.yml +++ b/.github/workflows/diff_shades_comment.yml @@ -33,7 +33,7 @@ jobs: - name: Try to find pre-existing PR comment if: steps.metadata.outputs.needs-comment == 'true' id: find-comment - uses: peter-evans/find-comment@1769778a0c5bd330272d749d12c036d65e70d39d + uses: peter-evans/find-comment@b657a70ff16d17651703a84bee1cb9ad9d2be2ea with: issue-number: ${{ steps.metadata.outputs.pr-number }} comment-author: "github-actions[bot]" @@ -41,7 +41,7 @@ jobs: - name: Create or update PR comment if: steps.metadata.outputs.needs-comment == 'true' - uses: peter-evans/create-or-update-comment@c9fcb64660bc90ec1cc535646af190c992007c32 + uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ steps.metadata.outputs.pr-number }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 86cf24df51b..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -language: python -cache: - pip: true - directories: - - $HOME/.cache/pre-commit -env: - - TEST_CMD="coverage run -m unittest" -install: - - pip install coverage coveralls pre-commit - - pip install -e '.[d]' -script: - - $TEST_CMD -after_success: - - coveralls -notifications: - on_success: change - on_failure: always -matrix: - include: - - name: "lint" - python: 3.7 - env: - - TEST_CMD="pre-commit run --all-files --show-diff-on-failure" - - name: "3.6" - python: 3.6 - - name: "3.7" - python: 3.7 - - name: "3.8" - python: 3.8 - - name: "3.9" - python: 3.9-dev - allow_failures: - - python: 3.9-dev -before_deploy: - - pip install pyinstaller - - pyinstaller --clean -F --add-data src/blib2to3/:blib2to3 src/black/__init__.py -deploy: - provider: releases - api_key: - secure: chYvcmnRqRKtfBcAZRj62rEv0ziWuHMl6MnfQbd1MOVQ4njntI8+CCPk118dW6MWSfwTqyMFy+t9gAgQYhjkLEHMS2aK9Z2wCWki1MkBrkMw5tYoLFvPu0KQ9rIVihxsr93a/am6Oh/Hp+1uuc4zWPUf1ubX+QlCzsxjCzVso1kTJjjdN04UxvkcFR+sY2d9Qyy9WcdifChnLwdmIJKIoVOE7Imm820nzImJHkJh8iSnjBjL98gvPPeC/nWTltsbErvf2mCv4NIjzjQZvHa87c7rSJGbliNrAxCSyyvBX+JNeS8U2fGLE83do0HieyjdPbTuc27e2nsrrihgPh+hXbiJerljclfp5hsJ5qGz5sS9MU1fR7sSLiQQ2v0TYB5RRwd34TgGiLwFAZZmgZOfMUCtefCKvP8qvELMSNd99+msfPEHiuhADF0bKPTbCUa6BgUHNr6woOLmHerjPHd6NI/a8Skz/uQB4xr3spLSmfUmX0fEqyYUDphkGPNH8IsvC1/F2isecW9kOzEWmB5oCmpMTGm4TIf3C01Nx+9PVwB2Z+30hhbfIEBxD4loRFmh/hU5TIQEpneF8yoIfe9EnMaoZbq86xhADZXvLIZvpXUdm1NQZDG6na2S1fwyOUKQsW6BWLcfoZZwZlrXrViD1jBsHBV++s+lxShTeTCszlo= - file: - - dist/black - skip_cleanup: true - on: - condition: $TRAVIS_PYTHON_VERSION == '3.6' - repo: psf/black - tags: true diff --git a/AUTHORS.md b/AUTHORS.md index f2d599dd878..a635e8c3c92 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -24,6 +24,7 @@ Multiple contributions by: - [Alex Vandiver](mailto:github@chmrr.net) - [Allan Simon](mailto:allan.simon@supinfo.com) - Anders-Petter Ljungquist +- [Amethyst Reese](mailto:amy@n7.gg) - [Andrew Thorp](mailto:andrew.thorp.dev@gmail.com) - [Andrew Zhou](mailto:andrewfzhou@gmail.com) - [Andrey](mailto:dyuuus@yandex.ru) diff --git a/CHANGES.md b/CHANGES.md index 4ff181674b1..4db961149bf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,44 +6,34 @@ -- Runtime support for Python 3.6 has been removed. Formatting 3.6 code will still be - supported until further notice. - ### Stable style -- Fix a crash when `# fmt: on` is used on a different block level than `# fmt: off` - (#3281) - ### Preview style -- Fix a crash when formatting some dicts with parenthesis-wrapped long string keys - (#3262) +- Enforce empty lines before classes and functions with sticky leading comments (#3302) +- Implicitly concatenated strings used as function args are now wrapped inside + parentheses (#3307) ### Configuration -- `.ipynb_checkpoints` directories are now excluded by default (#3293) - ### Packaging -- Executables made with PyInstaller will no longer crash when formatting several files - at once on macOS. Native x86-64 executables for macOS are available once again. - (#3275) -- Hatchling is now used as the build backend. This will not have any effect for users - who install Black with its wheels from PyPI. (#3233) -- Faster compiled wheels are now available for CPython 3.11 (#3276) - ### Parser +- Parsing support has been added for walruses inside generator expression that are + passed as function args (for example, + `any(match := my_re.match(text) for text in texts)`) (#3327). + ### Performance @@ -54,22 +44,61 @@ ### _Blackd_ -- Windows style (CRLF) newlines will be preserved (#3257). + ### Integrations -- Update GitHub Action to support formatting of Jupyter Notebook files via a `jupyter` - option (#3282) -- Update GitHub Action to support use of version specifiers (e.g. `<23`) for Black - version (#3265) - ### Documentation +## 22.10.0 + +### Highlights + +- Runtime support for Python 3.6 has been removed. Formatting 3.6 code will still be + supported until further notice. + +### Stable style + +- Fix a crash when `# fmt: on` is used on a different block level than `# fmt: off` + (#3281) + +### Preview style + +- Fix a crash when formatting some dicts with parenthesis-wrapped long string keys + (#3262) + +### Configuration + +- `.ipynb_checkpoints` directories are now excluded by default (#3293) +- Add `--skip-source-first-line` / `-x` option to ignore the first line of source code + while formatting (#3299) + +### Packaging + +- Executables made with PyInstaller will no longer crash when formatting several files + at once on macOS. Native x86-64 executables for macOS are available once again. + (#3275) +- Hatchling is now used as the build backend. This will not have any effect for users + who install Black with its wheels from PyPI. (#3233) +- Faster compiled wheels are now available for CPython 3.11 (#3276) + +### _Blackd_ + +- Windows style (CRLF) newlines will be preserved (#3257). + +### Integrations + +- Vim plugin: add flag (`g:black_preview`) to enable/disable the preview style (#3246) +- Update GitHub Action to support formatting of Jupyter Notebook files via a `jupyter` + option (#3282) +- Update GitHub Action to support use of version specifiers (e.g. `<23`) for Black + version (#3265) + ## 22.8.0 ### Highlights @@ -124,7 +153,6 @@ - Vim plugin: prefix messages with `Black: ` so it's clear they come from Black (#3194) - Docker: changed to a /opt/venv installation + added to PATH to be available to non-root users (#3202) -- Vim plugin: add flag (`g:black_preview`) to enable/disable the preview style (#3246) ### Output diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 44f57f6773c..00000000000 --- a/Pipfile +++ /dev/null @@ -1,35 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.python.org/simple" -verify_ssl = true - -[dev-packages] -Sphinx = ">=3.1.2" -coverage = "*" -docutils = "==0.15" # not a direct dependency, see https://github.com/pypa/pipenv/issues/3865 -flake8 = "*" -flake8-bugbear = "*" -flake8-mypy = "*" -mypy = ">=0.782" -pre-commit = "*" -readme_renderer = "*" -recommonmark = "*" -setuptools = ">=39.2.0" -setuptools-scm = "*" -twine = ">=1.11.0" -wheel = ">=0.31.1" -black = {editable = true, extras = ["d"], path = "."} - -[packages] -aiohttp = ">=3.3.2" -aiohttp-cors = "*" -appdirs = "*" -click = ">=7.0" -mypy_extensions = ">=0.4.3" -pathspec = ">=0.6" -regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = "==1.4.0" -typing_extensions = ">=3.7.4" -black = {editable = true,extras = ["d"],path = "."} -dataclasses = {"python_version <" = "3.7","version >" = "0.6"} diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 32b8012ff0e..00000000000 --- a/Pipfile.lock +++ /dev/null @@ -1,965 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "61d09a6b8a8c310becd5e108ed08e0eeae50c7323c08c8040367abded0cb1031" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "aiohttp": { - "hashes": [ - "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", - "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", - "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", - "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", - "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", - "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", - "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", - "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", - "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", - "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", - "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", - "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" - ], - "index": "pypi", - "version": "==3.6.2" - }, - "aiohttp-cors": { - "hashes": [ - "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e", - "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d" - ], - "index": "pypi", - "version": "==0.7.0" - }, - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "index": "pypi", - "version": "==1.4.4" - }, - "async-timeout": { - "hashes": [ - "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", - "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" - ], - "markers": "python_full_version >= '3.5.3'", - "version": "==3.0.1" - }, - "attrs": { - "hashes": [ - "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a", - "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.1.0" - }, - "black": { - "editable": true, - "extras": [ - "d" - ], - "path": "." - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" - ], - "index": "pypi", - "version": "==7.1.2" - }, - "dataclasses": { - "hashes": [ - "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f", - "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84" - ], - "index": "pypi", - "python_version <": "3.7", - "version": "==0.6", - "version >": "0.6" - }, - "idna": { - "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" - }, - "multidict": { - "hashes": [ - "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a", - "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000", - "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2", - "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507", - "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5", - "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7", - "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d", - "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463", - "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19", - "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3", - "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b", - "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c", - "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87", - "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7", - "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430", - "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255", - "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d" - ], - "markers": "python_version >= '3.5'", - "version": "==4.7.6" - }, - "mypy-extensions": { - "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" - ], - "index": "pypi", - "version": "==0.4.3" - }, - "pathspec": { - "hashes": [ - "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", - "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" - ], - "index": "pypi", - "version": "==0.8.0" - }, - "regex": { - "hashes": [ - "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204", - "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162", - "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f", - "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb", - "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6", - "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7", - "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88", - "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99", - "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644", - "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a", - "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840", - "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067", - "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd", - "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4", - "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e", - "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89", - "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e", - "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc", - "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf", - "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341", - "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7" - ], - "index": "pypi", - "version": "==2020.7.14" - }, - "setuptools-scm": { - "hashes": [ - "sha256:09c659d1d6680811c43f476a33c6d3d9872416580786e96bd29ea03e6a818e41", - "sha256:69258e2eeba5f7ce1ed7a5f109519580fa3578250f8e4d6684859f86d1b15826", - "sha256:731550a27e3901ee501c3bf99140b5436b8eeba341a9d19911cf364b8d573293", - "sha256:892e63b4983f9e30f9e8bf89ad03d2a02a47e6e5ced09b03ae6fe952ade8a579", - "sha256:a8994582e716ec690f33fec70cca0f85bd23ec974e3f783233e4879090a7faa8", - "sha256:b42c150c34d6120babf3646abd7513e032be2e230b3d2034f27404c65aa0c977", - "sha256:eaaec16b7af25c5f532b5af2332213bb6223d15cca4518f6dbc4c055641c86fd", - "sha256:efc928d6a64162c88cdc85fa4b84adfbd6dbf9f9b04319bc495eb16dcfaae00a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.1.2" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" - ], - "index": "pypi", - "version": "==0.10.1" - }, - "typed-ast": { - "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "index": "pypi", - "version": "==1.4.0" - }, - "typing-extensions": { - "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" - ], - "index": "pypi", - "version": "==3.7.4.3" - }, - "yarl": { - "hashes": [ - "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409", - "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593", - "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2", - "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8", - "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d", - "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692", - "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02", - "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a", - "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8", - "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6", - "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511", - "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e", - "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a", - "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb", - "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f", - "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317", - "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6" - ], - "markers": "python_version >= '3.5'", - "version": "==1.5.1" - } - }, - "develop": { - "aiohttp": { - "hashes": [ - "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", - "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326", - "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a", - "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654", - "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a", - "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4", - "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17", - "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec", - "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd", - "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48", - "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59", - "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965" - ], - "index": "pypi", - "version": "==3.6.2" - }, - "aiohttp-cors": { - "hashes": [ - "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e", - "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d" - ], - "index": "pypi", - "version": "==0.7.0" - }, - "alabaster": { - "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" - ], - "version": "==0.7.12" - }, - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "index": "pypi", - "version": "==1.4.4" - }, - "async-timeout": { - "hashes": [ - "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", - "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" - ], - "markers": "python_full_version >= '3.5.3'", - "version": "==3.0.1" - }, - "attrs": { - "hashes": [ - "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a", - "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.1.0" - }, - "babel": { - "hashes": [ - "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", - "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.0" - }, - "black": { - "editable": true, - "extras": [ - "d" - ], - "path": "." - }, - "bleach": { - "hashes": [ - "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f", - "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==3.1.5" - }, - "certifi": { - "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" - ], - "version": "==2020.6.20" - }, - "cfgv": { - "hashes": [ - "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d", - "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1" - ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.2.0" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" - ], - "index": "pypi", - "version": "==7.1.2" - }, - "colorama": { - "hashes": [ - "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", - "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.4.3" - }, - "commonmark": { - "hashes": [ - "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", - "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" - ], - "version": "==0.9.1" - }, - "coverage": { - "hashes": [ - "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb", - "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3", - "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716", - "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034", - "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3", - "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8", - "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0", - "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f", - "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4", - "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962", - "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d", - "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b", - "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4", - "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3", - "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258", - "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59", - "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01", - "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd", - "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b", - "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d", - "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89", - "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd", - "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b", - "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d", - "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46", - "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546", - "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082", - "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b", - "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4", - "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8", - "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811", - "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd", - "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651", - "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0" - ], - "index": "pypi", - "version": "==5.2.1" - }, - "distlib": { - "hashes": [ - "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", - "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" - ], - "version": "==0.3.1" - }, - "docutils": { - "hashes": [ - "sha256:54a349c622ff31c91cbec43b0b512f113b5b24daf00e2ea530bb1bd9aac14849", - "sha256:ba4584f9107571ced0d2c7f56a5499c696215ba90797849c92d395979da68521", - "sha256:d2ddba74835cb090a1b627d3de4e7835c628d07ee461f7b4480f51af2fe4d448" - ], - "index": "pypi", - "version": "==0.15" - }, - "filelock": { - "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" - ], - "version": "==3.0.12" - }, - "flake8": { - "hashes": [ - "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", - "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" - ], - "index": "pypi", - "version": "==3.8.3" - }, - "flake8-bugbear": { - "hashes": [ - "sha256:a3ddc03ec28ba2296fc6f89444d1c946a6b76460f859795b35b77d4920a51b63", - "sha256:bd02e4b009fb153fe6072c31c52aeab5b133d508095befb2ffcf3b41c4823162" - ], - "index": "pypi", - "version": "==20.1.4" - }, - "flake8-mypy": { - "hashes": [ - "sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18", - "sha256:cff009f4250e8391bf48990093cff85802778c345c8449d6498b62efefeebcbc" - ], - "index": "pypi", - "version": "==17.8.0" - }, - "identify": { - "hashes": [ - "sha256:9f5fcf22b665eaece583bd395b103c2769772a0f646ffabb5b1f155901b07de2", - "sha256:b1aa2e05863dc80242610d46a7b49105e2eafe00ef0c8ff311c1828680760c76" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.29" - }, - "idna": { - "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" - }, - "imagesize": { - "hashes": [ - "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", - "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.2.0" - }, - "jinja2": { - "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.11.2" - }, - "keyring": { - "hashes": [ - "sha256:182f94fc0381546489e3e4d90384a8c1d43cc09ffe2eb4a826e7312df6e1be7c", - "sha256:cd4d486803d55bdb13e2d453eb61dbbc984773e4f2b98a455aa85b1f4bc421e4" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3.1" - }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.1" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "multidict": { - "hashes": [ - "sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a", - "sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000", - "sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2", - "sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507", - "sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5", - "sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7", - "sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d", - "sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463", - "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19", - "sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3", - "sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b", - "sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c", - "sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87", - "sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7", - "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430", - "sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255", - "sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d" - ], - "markers": "python_version >= '3.5'", - "version": "==4.7.6" - }, - "mypy": { - "hashes": [ - "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c", - "sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86", - "sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b", - "sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd", - "sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc", - "sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea", - "sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e", - "sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308", - "sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406", - "sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d", - "sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707", - "sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d", - "sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c", - "sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a" - ], - "index": "pypi", - "version": "==0.782" - }, - "mypy-extensions": { - "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" - ], - "index": "pypi", - "version": "==0.4.3" - }, - "nodeenv": { - "hashes": [ - "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9", - "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c" - ], - "version": "==1.5.0" - }, - "packaging": { - "hashes": [ - "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", - "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.4" - }, - "pathspec": { - "hashes": [ - "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", - "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" - ], - "index": "pypi", - "version": "==0.8.0" - }, - "pkginfo": { - "hashes": [ - "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", - "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32" - ], - "version": "==1.5.0.1" - }, - "pre-commit": { - "hashes": [ - "sha256:810aef2a2ba4f31eed1941fc270e72696a1ad5590b9751839c90807d0fff6b9a", - "sha256:c54fd3e574565fe128ecc5e7d2f91279772ddb03f8729645fa812fe809084a70" - ], - "index": "pypi", - "version": "==2.7.1" - }, - "pycodestyle": { - "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.6.0" - }, - "pyflakes": { - "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.2.0" - }, - "pygments": { - "hashes": [ - "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", - "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" - ], - "markers": "python_version >= '3.5'", - "version": "==2.6.1" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" - }, - "pytz": { - "hashes": [ - "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", - "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" - ], - "version": "==2020.1" - }, - "pyyaml": { - "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "version": "==5.3.1" - }, - "readme-renderer": { - "hashes": [ - "sha256:cbe9db71defedd2428a1589cdc545f9bd98e59297449f69d721ef8f1cfced68d", - "sha256:cc4957a803106e820d05d14f71033092537a22daa4f406dfbdd61177e0936376" - ], - "index": "pypi", - "version": "==26.0" - }, - "recommonmark": { - "hashes": [ - "sha256:29cd4faeb6c5268c633634f2d69aef9431e0f4d347f90659fd0aab20e541efeb", - "sha256:2ec4207a574289355d5b6ae4ae4abb29043346ca12cdd5f07d374dc5987d2852" - ], - "index": "pypi", - "version": "==0.6.0" - }, - "regex": { - "hashes": [ - "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204", - "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162", - "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f", - "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb", - "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6", - "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7", - "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88", - "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99", - "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644", - "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a", - "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840", - "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067", - "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd", - "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4", - "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e", - "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89", - "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e", - "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc", - "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf", - "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341", - "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7" - ], - "index": "pypi", - "version": "==2020.7.14" - }, - "requests": { - "hashes": [ - "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", - "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.24.0" - }, - "requests-toolbelt": { - "hashes": [ - "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", - "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" - ], - "version": "==0.9.1" - }, - "rfc3986": { - "hashes": [ - "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d", - "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50" - ], - "version": "==1.4.0" - }, - "setuptools-scm": { - "hashes": [ - "sha256:09c659d1d6680811c43f476a33c6d3d9872416580786e96bd29ea03e6a818e41", - "sha256:69258e2eeba5f7ce1ed7a5f109519580fa3578250f8e4d6684859f86d1b15826", - "sha256:731550a27e3901ee501c3bf99140b5436b8eeba341a9d19911cf364b8d573293", - "sha256:892e63b4983f9e30f9e8bf89ad03d2a02a47e6e5ced09b03ae6fe952ade8a579", - "sha256:a8994582e716ec690f33fec70cca0f85bd23ec974e3f783233e4879090a7faa8", - "sha256:b42c150c34d6120babf3646abd7513e032be2e230b3d2034f27404c65aa0c977", - "sha256:eaaec16b7af25c5f532b5af2332213bb6223d15cca4518f6dbc4c055641c86fd", - "sha256:efc928d6a64162c88cdc85fa4b84adfbd6dbf9f9b04319bc495eb16dcfaae00a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.1.2" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" - }, - "snowballstemmer": { - "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" - ], - "version": "==2.0.0" - }, - "sphinx": { - "hashes": [ - "sha256:321d6d9b16fa381a5306e5a0b76cd48ffbc588e6340059a729c6fdd66087e0e8", - "sha256:ce6fd7ff5b215af39e2fcd44d4a321f6694b4530b6f2b2109b64d120773faea0" - ], - "index": "pypi", - "version": "==3.2.1" - }, - "sphinxcontrib-applehelp": { - "hashes": [ - "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", - "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-devhelp": { - "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-htmlhelp": { - "hashes": [ - "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f", - "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" - }, - "sphinxcontrib-jsmath": { - "hashes": [ - "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", - "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "sphinxcontrib-qthelp": { - "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" - }, - "sphinxcontrib-serializinghtml": { - "hashes": [ - "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", - "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" - ], - "markers": "python_version >= '3.5'", - "version": "==1.1.4" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" - ], - "index": "pypi", - "version": "==0.10.1" - }, - "tqdm": { - "hashes": [ - "sha256:1a336d2b829be50e46b84668691e0a2719f26c97c62846298dd5ae2937e4d5cf", - "sha256:564d632ea2b9cb52979f7956e093e831c28d441c11751682f84c86fc46e4fd21" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.48.2" - }, - "twine": { - "hashes": [ - "sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab", - "sha256:ba9ff477b8d6de0c89dd450e70b2185da190514e91c42cc62f96850025c10472" - ], - "index": "pypi", - "version": "==3.2.0" - }, - "typed-ast": { - "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "index": "pypi", - "version": "==1.4.0" - }, - "typing-extensions": { - "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" - ], - "index": "pypi", - "version": "==3.7.4.3" - }, - "urllib3": { - "hashes": [ - "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", - "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.25.10" - }, - "virtualenv": { - "hashes": [ - "sha256:43add625c53c596d38f971a465553f6318decc39d98512bc100fa1b1e839c8dc", - "sha256:e0305af10299a7fb0d69393d8f04cb2965dda9351140d11ac8db4e5e3970451b" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.0.31" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - }, - "wheel": { - "hashes": [ - "sha256:497add53525d16c173c2c1c733b8f655510e909ea78cc0e29d374243544b77a2", - "sha256:99a22d87add3f634ff917310a3d87e499f19e663413a52eb9232c447aa646c9f" - ], - "index": "pypi", - "version": "==0.35.1" - }, - "yarl": { - "hashes": [ - "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409", - "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593", - "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2", - "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8", - "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d", - "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692", - "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02", - "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a", - "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8", - "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6", - "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511", - "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e", - "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a", - "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb", - "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f", - "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317", - "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6" - ], - "markers": "python_version >= '3.5'", - "version": "==1.5.1" - } - } -} diff --git a/README.md b/README.md index 0ab059185f7..6b72a9b9a23 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,6 @@ If you can't wait for the latest _hotness_ and want to install from GitHub, use: `pip install git+https://github.com/psf/black` -#### Install from GitHub - -If you can't wait for the latest _hotness_ and want to install from GitHub, use: - -`pip install git+git://github.com/psf/black` - ### Usage To get started right away with sensible defaults: @@ -72,7 +66,7 @@ Further information can be found in our docs: _Black_ is already [successfully used](https://github.com/psf/black#used-by) by many projects, small and big. _Black_ has a comprehensive test suite, with efficient parallel tests, and our own auto formatting and parallel Continuous Integration runner. Now that -we have become stable, you should not expect large formatting to changes in the future. +we have become stable, you should not expect large formatting changes in the future. Stylistic changes will mostly be responses to bug reports and support for new Python syntax. For more information please refer to the [The Black Code Style](https://black.readthedocs.io/en/stable/the_black_code_style/index.html). diff --git a/autoload/black.vim b/autoload/black.vim index e87a1e4edfa..eec44637950 100644 --- a/autoload/black.vim +++ b/autoload/black.vim @@ -57,8 +57,8 @@ def _get_virtualenv_site_packages(venv_path, pyver): def _initialize_black_env(upgrade=False): pyver = sys.version_info[:3] - if pyver < (3, 6, 2): - print("Sorry, Black requires Python 3.6.2+ to run.") + if pyver < (3, 7): + print("Sorry, Black requires Python 3.7+ to run.") return False from pathlib import Path diff --git a/docs/black_primer.md b/docs/black_primer.md deleted file mode 100644 index a2dd964b7dc..00000000000 --- a/docs/black_primer.md +++ /dev/null @@ -1,120 +0,0 @@ -# black-primer - -`black-primer` is a tool built for CI (and humans) to have _Black_ `--check` a number of -(configured in `primer.json`) Git accessible projects in parallel. _(A PR will be -accepted to add Mercurial support.)_ - -## Run flow - -- Ensure we have a `black` + `git` in PATH -- Load projects from `primer.json` -- Run projects in parallel with `--worker` workers (defaults to CPU count / 2) - - Checkout projects - - Run black and record result - - Clean up repository checkout _(can optionally be disabled via `--keep`)_ -- Display results summary to screen -- Default to cleaning up `--work-dir` (which defaults to tempfile schemantics) -- Return - - 0 for successful run - - < 0 for environment / internal error - - \> 0 for each project with an error - -## Speed up runs 🏎 - -If you're running locally yourself to test black on lots of code try: - -- Using `-k` / `--keep` + `-w` / `--work-dir` so you don't have to re-checkout the repo - each run - -## CLI arguments - -```text -Usage: black-primer [OPTIONS] - - primer - prime projects for blackening... 🏴 - -Options: - -c, --config PATH JSON config file path [default: /Users/cooper/repos/ - black/src/black_primer/primer.json] - - --debug Turn on debug logging [default: False] - -k, --keep Keep workdir + repos post run [default: False] - -L, --long-checkouts Pull big projects to test [default: False] - -R, --rebase Rebase project if already checked out [default: - False] - - -w, --workdir PATH Directory path for repo checkouts [default: /var/fol - ders/tc/hbwxh76j1hn6gqjd2n2sjn4j9k1glp/T/primer.20200 - 517125229] - - -W, --workers INTEGER Number of parallel worker coroutines [default: 2] - -h, --help Show this message and exit. -``` - -## Primer config file - -The config file is in JSON format. Its main element is the `"projects"` dictionary and -each parameter is explained below: - -```json -{ - "projects": { - "00_Example": { - "cli_arguments": "List of extra CLI arguments to pass Black for this project", - "expect_formatting_changes": "Boolean to indicate that the version of Black is expected to cause changes", - "git_clone_url": "URL you would pass `git clone` to check out this repo", - "long_checkout": "Boolean to have repo skipped by default unless `--long-checkouts` is specified", - "py_versions": "List of major Python versions to run this project with - all will do as you'd expect - run on ALL versions" - }, - "aioexabgp": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/cooperlees/aioexabgp.git", - "long_checkout": false, - "py_versions": ["all", "3.8"] - } - } -} -``` - -An example primer config file is used by Black -[here](https://github.com/psf/black/blob/master/src/black_primer/primer.json) - -## Example run - -```console -cooper-mbp:black cooper$ ~/venvs/b/bin/black-primer -[2020-05-17 13:06:40,830] INFO: 4 projects to run Black over (lib.py:270) -[2020-05-17 13:06:44,215] INFO: Analyzing results (lib.py:285) --- primer results 📊 -- - -3 / 4 succeeded (75.0%) ✅ -1 / 4 FAILED (25.0%) 💩 - - 0 projects disabled by config - - 0 projects skipped due to Python version - - 0 skipped due to long checkout - -Failed projects: - -## flake8-bugbear: - - Returned 1 - - stdout: ---- tests/b303_b304.py 2020-05-17 20:04:09.991227 +0000 -+++ tests/b303_b304.py 2020-05-17 20:06:42.753851 +0000 -@@ -26,11 +26,11 @@ - maxint = 5 # this is okay - # the following should not crash - (a, b, c) = list(range(3)) - # it is different than this - a, b, c = list(range(3)) -- a, b, c, = list(range(3)) -+ a, b, c = list(range(3)) - # and different than this - (a, b), c = list(range(3)) - a, *b, c = [1, 2, 3, 4, 5] - b[1:3] = [0, 0] - -would reformat tests/b303_b304.py -Oh no! 💥 💔 💥 -1 file would be reformatted, 22 files would be left unchanged. -``` diff --git a/docs/contributing/reference/reference_classes.rst b/docs/contributing/reference/reference_classes.rst index fa765961e69..3931e0e0072 100644 --- a/docs/contributing/reference/reference_classes.rst +++ b/docs/contributing/reference/reference_classes.rst @@ -11,23 +11,29 @@ .. autoclass:: black.brackets.BracketTracker :members: -:class:`EmptyLineTracker` +:class:`Line` +------------- + +.. autoclass:: black.lines.Line + :members: + :special-members: __str__, __bool__ + +:class:`LinesBlock` ------------------------- -.. autoclass:: black.EmptyLineTracker +.. autoclass:: black.lines.LinesBlock :members: -:class:`Line` -------------- +:class:`EmptyLineTracker` +------------------------- -.. autoclass:: black.Line +.. autoclass:: black.lines.EmptyLineTracker :members: - :special-members: __str__, __bool__ :class:`LineGenerator` ---------------------- -.. autoclass:: black.LineGenerator +.. autoclass:: black.linegen.LineGenerator :show-inheritance: :members: @@ -40,7 +46,7 @@ :class:`Report` --------------- -.. autoclass:: black.Report +.. autoclass:: black.report.Report :members: :special-members: __str__ diff --git a/docs/contributing_to_black.md b/docs/contributing_to_black.md deleted file mode 100644 index e5307adb5d0..00000000000 --- a/docs/contributing_to_black.md +++ /dev/null @@ -1,70 +0,0 @@ -[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM CONTRIBUTING.md" - -# Contributing to _Black_ - -Welcome! Happy to see you willing to make the project better. Have you read the entire -[user documentation](https://black.readthedocs.io/en/latest/) yet? - -## Bird's eye view - -In terms of inspiration, _Black_ is about as configurable as _gofmt_. This is -deliberate. - -Bug reports and fixes are always welcome! Please follow the -[issue template on GitHub](https://github.com/psf/black/issues/new) for best results. - -Before you suggest a new feature or configuration knob, ask yourself why you want it. If -it enables better integration with some workflow, fixes an inconsistency, speeds things -up, and so on - go for it! On the other hand, if your answer is "because I don't like a -particular formatting" then you're not ready to embrace _Black_ yet. Such changes are -unlikely to get accepted. You can still try but prepare to be disappointed. - -## Technicalities - -Development on the latest version of Python is preferred. As of this writing it's 3.8. -You can use any operating system. I am using macOS myself and CentOS at work. - -Install all development dependencies using: - -```console -$ pipenv install --dev -$ pipenv shell -$ pre-commit install -``` - -If you haven't used `pipenv` before but are comfortable with virtualenvs, just run -`pip install pipenv` in the virtualenv you're already using and invoke the command above -from the cloned _Black_ repo. It will do the correct thing. - -Before submitting pull requests, run lints and tests with: - -```console -$ pre-commit run -a -$ python -m unittest -$ black-primer [-k -w /tmp/black_test_repos] -``` - -## black-primer - -`black-primer` is used by CI to pull down well-known _Black_ formatted projects and see -if we get source code changes. It will error on formatting changes or errors. Please run -before pushing your PR to see if you get the actions you would expect from _Black_ with -your PR. You may need to change -[primer.json](https://github.com/psf/black/blob/master/src/black_primer/primer.json) -configuration for it to pass. - -For more `black-primer` information visit the -[documentation](https://github.com/psf/black/blob/master/docs/black_primer.md). - -## Hygiene - -If you're fixing a bug, add a test. Run it first to confirm it fails, then fix the bug, -run it again to confirm it's really fixed. - -If adding a new feature, add a test. In fact, always add a test. But wait, before adding -any large feature, first open an issue for us to discuss the idea first. - -## Finally - -Thanks again for your interest in improving the project! You're taking action when most -people decide to sit and watch. diff --git a/docs/editor_integration.md b/docs/editor_integration.md deleted file mode 100644 index 73107d6a4a1..00000000000 --- a/docs/editor_integration.md +++ /dev/null @@ -1,288 +0,0 @@ -# Editor integration - -## Emacs - -Options include the following: - -- [purcell/reformatter.el](https://github.com/purcell/reformatter.el) -- [proofit404/blacken](https://github.com/pythonic-emacs/blacken) -- [Elpy](https://github.com/jorgenschaefer/elpy). - -## PyCharm/IntelliJ IDEA - -1. Install `black`. - -```console -$ pip install black -``` - -2. Locate your `black` installation folder. - -On macOS / Linux / BSD: - -```console -$ which black -/usr/local/bin/black # possible location -``` - -On Windows: - -```console -$ where black -%LocalAppData%\Programs\Python\Python36-32\Scripts\black.exe # possible location -``` - -Note that if you are using a virtual environment detected by PyCharm, this is an -unneeded step. In this case the path to `black` is `$PyInterpreterDirectory$/black`. - -3. Open External tools in PyCharm/IntelliJ IDEA - -On macOS: - -`PyCharm -> Preferences -> Tools -> External Tools` - -On Windows / Linux / BSD: - -`File -> Settings -> Tools -> External Tools` - -4. Click the + icon to add a new external tool with the following values: - - - Name: Black - - Description: Black is the uncompromising Python code formatter. - - Program: - - Arguments: `"$FilePath$"` - -5. Format the currently opened file by selecting `Tools -> External Tools -> black`. - - - Alternatively, you can set a keyboard shortcut by navigating to - `Preferences or Settings -> Keymap -> External Tools -> External Tools - Black`. - -6. Optionally, run _Black_ on every file save: - - 1. Make sure you have the - [File Watchers](https://plugins.jetbrains.com/plugin/7177-file-watchers) plugin - installed. - 2. Go to `Preferences or Settings -> Tools -> File Watchers` and click `+` to add a - new watcher: - - Name: Black - - File type: Python - - Scope: Project Files - - Program: - - Arguments: `$FilePath$` - - Output paths to refresh: `$FilePath$` - - Working directory: `$ProjectFileDir$` - - - Uncheck "Auto-save edited files to trigger the watcher" in Advanced Options - -## Wing IDE - -Wing supports black via the OS Commands tool, as explained in the Wing documentation on -[pep8 formatting](https://wingware.com/doc/edit/pep8). The detailed procedure is: - -1. Install `black`. - -```console -$ pip install black -``` - -2. Make sure it runs from the command line, e.g. - -```console -$ black --help -``` - -3. In Wing IDE, activate the **OS Commands** panel and define the command **black** to - execute black on the currently selected file: - -- Use the Tools -> OS Commands menu selection -- click on **+** in **OS Commands** -> New: Command line.. - - Title: black - - Command Line: black %s - - I/O Encoding: Use Default - - Key Binding: F1 - - [x] Raise OS Commands when executed - - [x] Auto-save files before execution - - [x] Line mode - -4. Select a file in the editor and press **F1** , or whatever key binding you selected - in step 3, to reformat the file. - -## Vim - -Commands and shortcuts: - -- `:Black` to format the entire file (ranges not supported); -- `:BlackUpgrade` to upgrade _Black_ inside the virtualenv; -- `:BlackVersion` to get the current version of _Black_ inside the virtualenv. - -Configuration: - -- `g:black_fast` (defaults to `0`) -- `g:black_linelength` (defaults to `88`) -- `g:black_skip_string_normalization` (defaults to `0`) -- `g:black_virtualenv` (defaults to `~/.vim/black` or `~/.local/share/nvim/black`) - -To install with [vim-plug](https://github.com/junegunn/vim-plug): - -``` -Plug 'psf/black', { 'branch': 'stable' } -``` - -or with [Vundle](https://github.com/VundleVim/Vundle.vim): - -``` -Plugin 'psf/black' -``` - -and execute the following in a terminal: - -```console -$ cd ~/.vim/bundle/black -$ git checkout origin/stable -b stable -``` - -or you can copy the plugin from -[plugin/black.vim](https://github.com/psf/black/blob/stable/plugin/black.vim). - -``` -mkdir -p ~/.vim/pack/python/start/black/plugin -curl https://raw.githubusercontent.com/psf/black/stable/plugin/black.vim -o ~/.vim/pack/python/start/black/plugin/black.vim -``` - -Let me know if this requires any changes to work with Vim 8's builtin `packadd`, or -Pathogen, and so on. - -This plugin **requires Vim 7.0+ built with Python 3.6+ support**. It needs Python 3.6 to -be able to run _Black_ inside the Vim process which is much faster than calling an -external command. - -On first run, the plugin creates its own virtualenv using the right Python version and -automatically installs _Black_. You can upgrade it later by calling `:BlackUpgrade` and -restarting Vim. - -If you need to do anything special to make your virtualenv work and install _Black_ (for -example you want to run a version from master), create a virtualenv manually and point -`g:black_virtualenv` to it. The plugin will use it. - -To run _Black_ on save, add the following line to `.vimrc` or `init.vim`: - -``` -autocmd BufWritePre *.py execute ':Black' -``` - -To run _Black_ on a key press (e.g. F9 below), add this: - -``` -nnoremap :Black -``` - -**How to get Vim with Python 3.6?** On Ubuntu 17.10 Vim comes with Python 3.6 by -default. On macOS with Homebrew run: `brew install vim`. When building Vim from source, -use: `./configure --enable-python3interp=yes`. There's many guides online how to do -this. - -**I get an import error when using _Black_ from a virtual environment**: If you get an -error message like this: - -```text -Traceback (most recent call last): - File "", line 63, in - File "/home/gui/.vim/black/lib/python3.7/site-packages/black.py", line 45, in - from typed_ast import ast3, ast27 - File "/home/gui/.vim/black/lib/python3.7/site-packages/typed_ast/ast3.py", line 40, in - from typed_ast import _ast3 -ImportError: /home/gui/.vim/black/lib/python3.7/site-packages/typed_ast/_ast3.cpython-37m-x86_64-linux-gnu.so: undefined symbool: PyExc_KeyboardInterrupt -``` - -Then you need to install `typed_ast` and `regex` directly from the source code. The -error happens because `pip` will download [Python wheels](https://pythonwheels.com/) if -they are available. Python wheels are a new standard of distributing Python packages and -packages that have Cython and extensions written in C are already compiled, so the -installation is much more faster. The problem here is that somehow the Python -environment inside Vim does not match with those already compiled C extensions and these -kind of errors are the result. Luckily there is an easy fix: installing the packages -from the source code. - -The two packages that cause the problem are: - -- [regex](https://pypi.org/project/regex/) -- [typed-ast](https://pypi.org/project/typed-ast/) - -Now remove those two packages: - -```console -$ pip uninstall regex typed-ast -y -``` - -And now you can install them with: - -```console -$ pip install --no-binary :all: regex typed-ast -``` - -The C extensions will be compiled and now Vim's Python environment will match. Note that -you need to have the GCC compiler and the Python development files installed (on -Ubuntu/Debian do `sudo apt-get install build-essential python3-dev`). - -If you later want to update _Black_, you should do it like this: - -```console -$ pip install -U black --no-binary regex,typed-ast -``` - -## Visual Studio Code - -Use the -[Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) -([instructions](https://code.visualstudio.com/docs/python/editing#_formatting)). - -## SublimeText 3 - -Use [sublack plugin](https://github.com/jgirardet/sublack). - -## Jupyter Notebook Magic - -Use [blackcellmagic](https://github.com/csurfer/blackcellmagic). - -## Python Language Server - -If your editor supports the [Language Server Protocol](https://langserver.org/) (Atom, -Sublime Text, Visual Studio Code and many more), you can use the -[Python Language Server](https://github.com/palantir/python-language-server) with the -[pyls-black](https://github.com/rupert/pyls-black) plugin. - -## Atom/Nuclide - -Use [python-black](https://atom.io/packages/python-black). - -## Gradle (the build tool) - -Use the [Spotless](https://github.com/diffplug/spotless/tree/main/plugin-gradle) plugin. - -## Kakoune - -Add the following hook to your kakrc, then run _Black_ with `:format`. - -``` -hook global WinSetOption filetype=python %{ - set-option window formatcmd 'black -q -' -} -``` - -## Thonny - -Use [Thonny-black-code-format](https://github.com/Franccisco/thonny-black-code-format). - -## Other integrations - -Other editors and tools will require external contributions. - -Patches welcome! ✨ 🍰 ✨ - -Any tool that can pipe code through _Black_ using its stdio mode (just -[use `-` as the file name](https://www.tldp.org/LDP/abs/html/special-chars.html#DASHREF2)). -The formatted code will be returned on stdout (unless `--check` was passed). _Black_ -will still emit messages on stderr but that shouldn't affect your use case. - -This can be used for example with PyCharm's or IntelliJ's -[File Watchers](https://www.jetbrains.com/help/pycharm/file-watchers.html). diff --git a/docs/faq.md b/docs/faq.md index 8b9ffb0202e..bc9deccb756 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -95,7 +95,7 @@ Support for formatting Python 2 code was removed in version 22.0. While we've ma plans to stop supporting older Python 3 minor versions immediately, their support might also be removed some time in the future without a deprecation period. -Runtime support for 3.6 was removed in version 22.9.0. +Runtime support for 3.6 was removed in version 22.10.0. ## Why does my linter or typechecker complain after I format my code? diff --git a/docs/github_actions.md b/docs/github_actions.md deleted file mode 100644 index 7ff87540242..00000000000 --- a/docs/github_actions.md +++ /dev/null @@ -1,19 +0,0 @@ -[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM README.md" - -# GitHub Actions - -Create a file named `.github/workflows/black.yml` inside your repository with: - -```yaml -name: Lint - -on: [push, pull_request] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: psf/black@stable -``` diff --git a/docs/ignoring_unmodified_files.md b/docs/ignoring_unmodified_files.md deleted file mode 100644 index a915f4e8678..00000000000 --- a/docs/ignoring_unmodified_files.md +++ /dev/null @@ -1,23 +0,0 @@ -[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM README.md" - -# Ignoring unmodified files - -_Black_ remembers files it has already formatted, unless the `--diff` flag is used or -code is passed via standard input. This information is stored per-user. The exact -location of the file depends on the _Black_ version and the system on which _Black_ is -run. The file is non-portable. The standard location on common operating systems is: - -- Windows: - `C:\\Users\\AppData\Local\black\black\Cache\\cache...pickle` -- macOS: - `/Users//Library/Caches/black//cache...pickle` -- Linux: - `/home//.cache/black//cache...pickle` - -`file-mode` is an int flag that determines whether the file was formatted as 3.6+ only, -as .pyi, and whether string normalization was omitted. - -To override the location of these files on macOS or Linux, set the environment variable -`XDG_CACHE_HOME` to your preferred location. For example, if you want to put the cache -in the directory you're running _Black_ from, set `XDG_CACHE_HOME=.cache`. _Black_ will -then write the above files to `.cache/black//`. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index f03d247d949..00000000000 --- a/docs/index.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. black documentation master file, created by - sphinx-quickstart on Fri Mar 23 10:53:30 2018. - -The uncompromising code formatter -================================= - -By using *Black*, you agree to cede control over minutiae of -hand-formatting. In return, *Black* gives you speed, determinism, and -freedom from `pycodestyle` nagging about formatting. You will save time -and mental energy for more important matters. - -*Black* makes code review faster by producing the smallest diffs -possible. Blackened code looks the same regardless of the project -you're reading. Formatting becomes transparent after a while and you -can focus on the content instead. - -Try it out now using the `Black Playground `_. - -.. note:: - - `Black is beta `_. - - -Testimonials ------------- - -**Dusty Phillips**, `writer `_: - - *Black is opinionated so you don't have to be.* - -**Hynek Schlawack**, creator of `attrs `_, core -developer of Twisted and CPython: - - *An auto-formatter that doesn't suck is all I want for Xmas!* - -**Carl Meyer**, `Django `_ core developer: - - *At least the name is good.* - -**Kenneth Reitz**, creator of `requests `_ -and `pipenv `_: - - *This vastly improves the formatting of our code. Thanks a ton!* - -Contents --------- - -.. toctree:: - :maxdepth: 2 - - installation_and_usage - the_black_code_style - pyproject_toml - compatible_configs - editor_integration - blackd - black_primer - version_control_integration - github_actions - ignoring_unmodified_files - contributing_to_black - show_your_style - change_log - reference/reference_summary - authors - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`search` diff --git a/docs/installation_and_usage.md b/docs/installation_and_usage.md deleted file mode 100644 index cc0269198a2..00000000000 --- a/docs/installation_and_usage.md +++ /dev/null @@ -1,179 +0,0 @@ -[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM README.md" - -# Installation and usage - -## Installation - -_Black_ can be installed by running `pip install black`. It requires Python 3.6.0+ to -run but you can reformat Python 2 code with it, too. - -### Install from GitHub - -If you can't wait for the latest _hotness_ and want to install from GitHub, use: - -`pip install git+git://github.com/psf/black` - -## Usage - -To get started right away with sensible defaults: - -```sh -black {source_file_or_directory} -``` - -You can run _Black_ as a package if running it as a script doesn't work: - -```sh -python -m black {source_file_or_directory} -``` - -## Command line options - -_Black_ doesn't provide many options. You can list them by running `black --help`: - -```text -Usage: black [OPTIONS] [SRC]... - - The uncompromising code formatter. - -Options: - -c, --code TEXT Format the code passed in as a string. - -l, --line-length INTEGER How many characters per line to allow. - [default: 88] - - -t, --target-version [py27|py33|py34|py35|py36|py37|py38] - Python versions that should be supported by - Black's output. [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 reformatted. Return code 123 means - there was an internal error. - - --diff Don't write the files back, just output a - diff for each file on stdout. - - --color / --no-color Show colored diff. Only applies when - `--diff` is given. - - --fast / --safe If --fast given, skip temporary sanity - checks. [default: --safe] - - --include TEXT A regular expression that matches files and - directories that should be 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. - [default: \.pyi?$] - - --exclude TEXT A regular expression that matches files and - directories that should be 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. [default: /(\.eggs|\.git|\.hg|\.mypy - _cache|\.nox|\.tox|\.venv|\.svn|_build|buck- - out|build|dist)/] - - --force-exclude TEXT Like --exclude, but files and directories - matching this regex will be excluded even - when they are passed explicitly as arguments - - -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. - --config FILE Read configuration from FILE path. - -h, --help Show this message and exit. -``` - -_Black_ is a well-behaved Unix-style command-line tool: - -- it does nothing if no sources are passed to it; -- it will read from standard input and write to standard output if `-` is used as the - filename; -- it only outputs messages to users on standard error; -- exits with code 0 unless an internal error occurred (or `--check` was used). - -## Using _Black_ with other tools - -While _Black_ enforces formatting that conforms to PEP 8, other tools may raise warnings -about _Black_'s changes or will overwrite _Black_'s changes. A good example of this is -[isort](https://pypi.org/p/isort). Since _Black_ is barely configurable, these tools -should be configured to neither warn about nor overwrite _Black_'s changes. - -Actual details on _Black_ compatible configurations for various tools can be found in -[compatible_configs](https://github.com/psf/black/blob/master/docs/compatible_configs.md). - -## Migrating your code style without ruining git blame - -A long-standing argument against moving to automated code formatters like _Black_ is -that the migration will clutter up the output of `git blame`. This was a valid argument, -but since Git version 2.23, Git natively supports -[ignoring revisions in blame](https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revltrevgt) -with the `--ignore-rev` option. You can also pass a file listing the revisions to ignore -using the `--ignore-revs-file` option. The changes made by the revision will be ignored -when assigning blame. Lines modified by an ignored revision will be blamed on the -previous revision that modified those lines. - -So when migrating your project's code style to _Black_, reformat everything and commit -the changes (preferably in one massive commit). Then put the full 40 characters commit -identifier(s) into a file. - -``` -# Migrate code style to Black -5b4ab991dede475d393e9d69ec388fd6bd949699 -``` - -Afterwards, you can pass that file to `git blame` and see clean and meaningful blame -information. - -```console -$ git blame important.py --ignore-revs-file .git-blame-ignore-revs -7a1ae265 (John Smith 2019-04-15 15:55:13 -0400 1) def very_important_function(text, file): -abdfd8b0 (Alice Doe 2019-09-23 11:39:32 -0400 2) text = text.lstrip() -7a1ae265 (John Smith 2019-04-15 15:55:13 -0400 3) with open(file, "r+") as f: -7a1ae265 (John Smith 2019-04-15 15:55:13 -0400 4) f.write(formatted) -``` - -You can even configure `git` to automatically ignore revisions listed in a file on every -call to `git blame`. - -```console -$ git config blame.ignoreRevsFile .git-blame-ignore-revs -``` - -**The one caveat is that GitHub and GitLab do not yet support ignoring revisions using -their native UI of blame.** So blame information will be cluttered with a reformatting -commit on those platforms. (If you'd like this feature, there's an open issue for -[GitLab](https://gitlab.com/gitlab-org/gitlab/-/issues/31423) and please let GitHub -know!) - -## NOTE: This is a beta product - -_Black_ is already [successfully used](#used-by) by many projects, small and big. It -also sports a decent test suite. However, it is still very new. Things will probably be -wonky for a while. This is made explicit by the "Beta" trove classifier, as well as by -the "b" in the version number. What this means for you is that **until the formatter -becomes stable, you should expect some formatting to change in the future**. That being -said, no drastic stylistic changes are planned, mostly responses to bug reports. - -Also, as a temporary safety measure, _Black_ will check that the reformatted code still -produces a valid AST that is equivalent to the original. This slows it down. If you're -feeling confident, use `--fast`. diff --git a/docs/integrations/github_actions.md b/docs/integrations/github_actions.md index 12bcb21fee6..ebfcc2d95a2 100644 --- a/docs/integrations/github_actions.md +++ b/docs/integrations/github_actions.md @@ -44,7 +44,9 @@ extra. Installing the extra and including Jupyter Notebook files can be configur `jupyter` (default is `false`). You can also configure the arguments passed to _Black_ via `options` (defaults to -`'--check --diff'`) and `src` (default is `'.'`) +`'--check --diff'`) and `src` (default is `'.'`). Please note that the +[`--check` flag](labels/exit-code) is required so that the workflow fails if _Black_ +finds files that need to be formatted. Here's an example configuration: diff --git a/docs/integrations/source_version_control.md b/docs/integrations/source_version_control.md index 31d0df27273..4189b5c4b06 100644 --- a/docs/integrations/source_version_control.md +++ b/docs/integrations/source_version_control.md @@ -7,7 +7,7 @@ Use [pre-commit](https://pre-commit.com/). Once you ```yaml repos: - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 22.10.0 hooks: - id: black # It is recommended to specify the latest version of Python diff --git a/docs/pyproject_toml.md b/docs/pyproject_toml.md deleted file mode 100644 index cd313452b1e..00000000000 --- a/docs/pyproject_toml.md +++ /dev/null @@ -1,88 +0,0 @@ -[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM README.md" - -# pyproject.toml - -_Black_ is able to read project-specific default values for its command line options -from a `pyproject.toml` file. This is especially useful for specifying custom -`--include` and `--exclude` patterns for your project. - -**Pro-tip**: If you're asking yourself "Do I need to configure anything?" the answer is -"No". _Black_ is all about sensible defaults. - -## What on Earth is a `pyproject.toml` file? - -[PEP 518](https://www.python.org/dev/peps/pep-0518/) defines `pyproject.toml` as a -configuration file to store build system requirements for Python projects. With the help -of tools like [Poetry](https://python-poetry.org/) or -[Flit](https://flit.readthedocs.io/en/latest/) it can fully replace the need for -`setup.py` and `setup.cfg` files. - -## Where _Black_ looks for the file - -By default _Black_ looks for `pyproject.toml` starting from the common base directory of -all files and directories passed on the command line. If it's not there, it looks in -parent directories. It stops looking when it finds the file, or a `.git` directory, or a -`.hg` directory, or the root of the file system, whichever comes first. - -If you're formatting standard input, _Black_ will look for configuration starting from -the current working directory. - -You can also explicitly specify the path to a particular file that you want with -`--config`. In this situation _Black_ will not look for any other file. - -If you're running with `--verbose`, you will see a blue message if a file was found and -used. - -Please note `blackd` will not use `pyproject.toml` configuration. - -## Configuration format - -As the file extension suggests, `pyproject.toml` is a -[TOML](https://github.com/toml-lang/toml) file. It contains separate sections for -different tools. _Black_ is using the `[tool.black]` section. The option keys are the -same as long names of options on the command line. - -Note that you have to use single-quoted strings in TOML for regular expressions. It's -the equivalent of r-strings in Python. Multiline strings are treated as verbose regular -expressions by Black. Use `[ ]` to denote a significant space character. - -
-Example pyproject.toml - -```toml -[tool.black] -line-length = 88 -target-version = ['py37'] -include = '\.pyi?$' -exclude = ''' - -( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - )/ - | foo.py # also separately exclude a file named foo.py in - # the root of the project -) -''' -``` - -
- -## Lookup hierarchy - -Command-line options have defaults that you can see in `--help`. A `pyproject.toml` can -override those defaults. Finally, options provided by the user on the command line -override both. - -_Black_ will only ever use one `pyproject.toml` file during an entire run. It doesn't -look for multiple files, and doesn't compose configuration from different levels of the -file hierarchy. diff --git a/docs/reference/reference_functions.rst b/docs/reference/reference_functions.rst deleted file mode 100644 index a7184115c94..00000000000 --- a/docs/reference/reference_functions.rst +++ /dev/null @@ -1,180 +0,0 @@ -*Black* functions -================= - -*Contents are subject to change.* - -.. currentmodule:: black - -Assertions and checks ---------------------- - -.. autofunction:: black.assert_equivalent - -.. autofunction:: black.assert_stable - -.. autofunction:: black.can_be_split - -.. autofunction:: black.can_omit_invisible_parens - -.. autofunction:: black.is_empty_tuple - -.. autofunction:: black.is_import - -.. autofunction:: black.is_line_short_enough - -.. autofunction:: black.is_multiline_string - -.. autofunction:: black.is_one_tuple - -.. autofunction:: black.is_split_after_delimiter - -.. autofunction:: black.is_split_before_delimiter - -.. autofunction:: black.is_stub_body - -.. autofunction:: black.is_stub_suite - -.. autofunction:: black.is_vararg - -.. autofunction:: black.is_yield - - -Formatting ----------- - -.. autofunction:: black.format_file_contents - -.. autofunction:: black.format_file_in_place - -.. autofunction:: black.format_stdin_to_stdout - -.. autofunction:: black.format_str - -.. autofunction:: black.reformat_one - -.. autofunction:: black.schedule_formatting - -File operations ---------------- - -.. autofunction:: black.dump_to_file - -.. autofunction:: black.find_project_root - -.. autofunction:: black.gen_python_files - -.. autofunction:: black.read_pyproject_toml - -Parsing -------- - -.. autofunction:: black.decode_bytes - -.. autofunction:: black.lib2to3_parse - -.. autofunction:: black.lib2to3_unparse - -Split functions ---------------- - -.. autofunction:: black.bracket_split_build_line - -.. autofunction:: black.bracket_split_succeeded_or_raise - -.. autofunction:: black.delimiter_split - -.. autofunction:: black.left_hand_split - -.. autofunction:: black.right_hand_split - -.. autofunction:: black.standalone_comment_split - -.. autofunction:: black.transform_line - -Caching -------- - -.. autofunction:: black.filter_cached - -.. autofunction:: black.get_cache_file - -.. autofunction:: black.get_cache_info - -.. autofunction:: black.read_cache - -.. autofunction:: black.write_cache - -Utilities ---------- - -.. py:function:: black.DebugVisitor.show(code: str) -> None - - Pretty-print the lib2to3 AST of a given string of `code`. - -.. autofunction:: black.cancel - -.. autofunction:: black.child_towards - -.. autofunction:: black.container_of - -.. autofunction:: black.convert_one_fmt_off_pair - -.. autofunction:: black.diff - -.. autofunction:: black.dont_increase_indentation - -.. autofunction:: black.format_float_or_int_string - -.. autofunction:: black.ensure_visible - -.. autofunction:: black.enumerate_reversed - -.. autofunction:: black.enumerate_with_length - -.. autofunction:: black.generate_comments - -.. autofunction:: black.generate_ignored_nodes - -.. autofunction:: black.is_fmt_on - -.. autofunction:: black.contains_fmt_on_at_column - -.. autofunction:: black.first_leaf_column - -.. autofunction:: black.generate_trailers_to_omit - -.. autofunction:: black.get_future_imports - -.. autofunction:: black.list_comments - -.. autofunction:: black.make_comment - -.. autofunction:: black.maybe_make_parens_invisible_in_atom - -.. autofunction:: black.max_delimiter_priority_in_atom - -.. autofunction:: black.normalize_fmt_off - -.. autofunction:: black.normalize_numeric_literal - -.. autofunction:: black.normalize_prefix - -.. autofunction:: black.normalize_string_prefix - -.. autofunction:: black.normalize_string_quotes - -.. autofunction:: black.normalize_invisible_parens - -.. autofunction:: black.patch_click - -.. autofunction:: black.preceding_leaf - -.. autofunction:: black.re_compile_maybe_verbose - -.. autofunction:: black.should_split_body_explode - -.. autofunction:: black.shutdown - -.. autofunction:: black.sub_twice - -.. autofunction:: black.whitespace diff --git a/docs/requirements.txt b/docs/requirements.txt index 3c4b43511f6..c2342a35d7b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,7 @@ # Used by ReadTheDocs; pinned requirements for stability. myst-parser==0.18.1 -Sphinx==5.2.3 +Sphinx==5.3.0 # Older versions break Sphinx even though they're declared to be supported. docutils==0.19 sphinxcontrib-programoutput==0.17 diff --git a/docs/show_your_style.md b/docs/show_your_style.md deleted file mode 100644 index 67b213c3965..00000000000 --- a/docs/show_your_style.md +++ /dev/null @@ -1,19 +0,0 @@ -[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM README.md" - -# Show your style - -Use the badge in your project's README.md: - -```md -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -``` - -Using the badge in README.rst: - -``` -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black -``` - -Looks like this: -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index a028a2888ed..17b7eef092f 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -63,26 +63,47 @@ limit. Line continuation backslashes are converted into parenthesized strings. Unnecessary parentheses are stripped. The stability and status of this feature is tracked in [this issue](https://github.com/psf/black/issues/2188). -### Removing newlines in the beginning of code blocks +### Improved empty line management -_Black_ will remove newlines in the beginning of new code blocks, i.e. when the -indentation level is increased. For example: +1. _Black_ will remove newlines in the beginning of new code blocks, i.e. when the + indentation level is increased. For example: -```python -def my_func(): + ```python + def my_func(): - print("The line above me will be deleted!") -``` + print("The line above me will be deleted!") + ``` -will be changed to: + will be changed to: + + ```python + def my_func(): + print("The line above me will be deleted!") + ``` + + This new feature will be applied to **all code blocks**: `def`, `class`, `if`, + `for`, `while`, `with`, `case` and `match`. + +2. _Black_ will enforce empty lines before classes and functions with leading comments. + For example: + + ```python + some_var = 1 + # Leading sticky comment + def my_func(): + ... + ``` + + will be changed to: + + ```python + some_var = 1 -```python -def my_func(): - print("The line above me will be deleted!") -``` -This new feature will be applied to **all code blocks**: `def`, `class`, `if`, `for`, -`while`, `with`, `case` and `match`. + # Leading sticky comment + def my_func(): + ... + ``` ### Improved parentheses management diff --git a/docs/usage_and_configuration/black_as_a_server.md b/docs/usage_and_configuration/black_as_a_server.md index a2d4252109a..f24fb34d915 100644 --- a/docs/usage_and_configuration/black_as_a_server.md +++ b/docs/usage_and_configuration/black_as_a_server.md @@ -50,6 +50,9 @@ is rejected with `HTTP 501` (Not Implemented). The headers controlling how source code is formatted are: - `X-Line-Length`: corresponds to the `--line-length` command line flag. +- `X-Skip-Source-First-Line`: corresponds to the `--skip-source-first-line` command line + flag. If present and its value is not an empty string, the first line of the source + code will be ignored. - `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization` command line flag. If present and its value is not the empty string, no string normalization will be performed. diff --git a/docs/usage_and_configuration/the_basics.md b/docs/usage_and_configuration/the_basics.md index aa176c4ba3f..80897532a68 100644 --- a/docs/usage_and_configuration/the_basics.md +++ b/docs/usage_and_configuration/the_basics.md @@ -76,6 +76,8 @@ _Black_ to just tell you what it _would_ do without actually rewriting the Pytho There's two variations to this mode that are independently enabled by their respective flags. Both variations can be enabled at once. +(labels/exit-code)= + #### Exit code Passing `--check` will make _Black_ exit with: @@ -173,7 +175,7 @@ You can check the version of _Black_ you have installed using the `--version` fl ```console $ black --version -black, version 22.8.0 +black, version 22.10.0 ``` An option to require a specific version to be running is also provided. diff --git a/docs/version_control_integration.md b/docs/version_control_integration.md deleted file mode 100644 index 25dac308c47..00000000000 --- a/docs/version_control_integration.md +++ /dev/null @@ -1,28 +0,0 @@ -[//]: # "NOTE: THIS FILE WAS AUTOGENERATED FROM README.md" - -# Version control integration - -Use [pre-commit](https://pre-commit.com/). Once you -[have it installed](https://pre-commit.com/#install), add this to the -`.pre-commit-config.yaml` in your repository: - -```yaml -repos: - - repo: https://github.com/psf/black - rev: 19.10b0 # Replace by any tag/version: https://github.com/psf/black/tags - hooks: - - id: black - language_version: python3 # Should be a command that runs python3.6+ -``` - -Then run `pre-commit install` and you're ready to go. - -Avoid using `args` in the hook. Instead, store necessary configuration in -`pyproject.toml` so that editors and command-line usage of Black all behave consistently -for your project. See _Black_'s own -[pyproject.toml](https://github.com/psf/black/blob/master/pyproject.toml) for an -example. - -If you're already using Python 3.7, switch the `language_version` accordingly. Finally, -`stable` is a branch that tracks the latest release on PyPI. If you'd rather run on -master, this is also an option. diff --git a/fuzz.py b/fuzz.py deleted file mode 100644 index fdd4917f2ec..00000000000 --- a/fuzz.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Property-based tests for Black. - -By Zac Hatfield-Dodds, based on my Hypothesmith tool for source code -generation. You can run this file with `python`, `pytest`, or (soon) -a coverage-guided fuzzer I'm working on. -""" - -import hypothesmith -from hypothesis import HealthCheck, given, settings, strategies as st - -import black - - -# This test uses the Hypothesis and Hypothesmith libraries to generate random -# syntatically-valid Python source code and run Black in odd modes. -@settings( - max_examples=1000, # roughly 1k tests/minute, or half that under coverage - derandomize=True, # deterministic mode to avoid CI flakiness - deadline=None, # ignore Hypothesis' health checks; we already know that - suppress_health_check=HealthCheck.all(), # this is slow and filter-heavy. -) -@given( - # Note that while Hypothesmith might generate code unlike that written by - # humans, it's a general test that should pass for any *valid* source code. - # (so e.g. running it against code scraped of the internet might also help) - src_contents=hypothesmith.from_grammar() | hypothesmith.from_node(), - # Using randomly-varied modes helps us to exercise less common code paths. - mode=st.builds( - black.FileMode, - line_length=st.just(88) | st.integers(0, 200), - string_normalization=st.booleans(), - is_pyi=st.booleans(), - ), -) -def test_idempotent_any_syntatically_valid_python( - src_contents: str, mode: black.FileMode -) -> None: - # Before starting, let's confirm that the input string is valid Python: - compile(src_contents, "", "exec") # else the bug is in hypothesmith - - # Then format the code... - try: - dst_contents = black.format_str(src_contents, mode=mode) - except black.InvalidInput: - # This is a bug - if it's valid Python code, as above, black should be - # able to code with it. See issues #970, #1012, #1358, and #1557. - # TODO: remove this try-except block when issues are resolved. - return - - # And check that we got equivalent and stable output. - black.assert_equivalent(src_contents, dst_contents) - black.assert_stable(src_contents, dst_contents, mode=mode) - - # Future test: check that pure-python and mypyc versions of black - # give identical output for identical input? - - -if __name__ == "__main__": - test_idempotent_any_syntatically_valid_python() diff --git a/mypy.ini b/mypy.ini index 4811cc0be76..58bb7536173 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,7 +2,7 @@ # Specify the target platform details in config, so your developers are # free to run mypy on Windows, Linux, or macOS and get consistent # results. -python_version=3.6 +python_version=3.7 mypy_path=src @@ -19,11 +19,6 @@ no_implicit_reexport = False # to avoid 'em in the first place. warn_unreachable=True -[mypy-black] -# The following is because of `patch_click()`. Remove when -# we drop Python 3.6 support. -warn_unused_ignores=False - [mypy-blib2to3.driver.*] ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml index 554d7d07bf3..329adaa65d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ build-backend = "hatchling.build" [project] name = "black" description = "The uncompromising code formatter." -license = "MIT" +license = { text = "MIT" } requires-python = ">=3.7" authors = [ { name = "Łukasz Langa", email = "lukasz@langa.pl" }, diff --git a/setup.py b/setup.py deleted file mode 100644 index 12fde2568cf..00000000000 --- a/setup.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (C) 2020 Łukasz Langa -from setuptools import setup -import sys -import os - -assert sys.version_info >= (3, 6, 0), "black requires Python 3.6+" -from pathlib import Path # noqa E402 - -CURRENT_DIR = Path(__file__).parent -sys.path.insert(0, str(CURRENT_DIR)) # for setuptools.build_meta - - -def get_long_description() -> str: - return ( - (CURRENT_DIR / "README.md").read_text(encoding="utf8") - + "\n\n" - + (CURRENT_DIR / "CHANGES.md").read_text(encoding="utf8") - ) - - -USE_MYPYC = False -# To compile with mypyc, a mypyc checkout must be present on the PYTHONPATH -if len(sys.argv) > 1 and sys.argv[1] == "--use-mypyc": - sys.argv.pop(1) - USE_MYPYC = True -if os.getenv("BLACK_USE_MYPYC", None) == "1": - USE_MYPYC = True - -if USE_MYPYC: - mypyc_targets = [ - "src/black/__init__.py", - "src/blib2to3/pytree.py", - "src/blib2to3/pygram.py", - "src/blib2to3/pgen2/parse.py", - "src/blib2to3/pgen2/grammar.py", - "src/blib2to3/pgen2/token.py", - "src/blib2to3/pgen2/driver.py", - "src/blib2to3/pgen2/pgen.py", - ] - - from mypyc.build import mypycify - - opt_level = os.getenv("MYPYC_OPT_LEVEL", "3") - ext_modules = mypycify(mypyc_targets, opt_level=opt_level) -else: - ext_modules = [] - -setup( - name="black", - use_scm_version={ - "write_to": "src/_black_version.py", - "write_to_template": 'version = "{version}"\n', - }, - description="The uncompromising code formatter.", - long_description=get_long_description(), - long_description_content_type="text/markdown", - keywords="automation formatter yapf autopep8 pyfmt gofmt rustfmt", - author="Łukasz Langa", - author_email="lukasz@langa.pl", - url="https://github.com/psf/black", - project_urls={"Changelog": "https://github.com/psf/black/blob/master/CHANGES.md"}, - license="MIT", - py_modules=["_black_version"], - ext_modules=ext_modules, - packages=["blackd", "black", "blib2to3", "blib2to3.pgen2", "black_primer"], - package_dir={"": "src"}, - package_data={"blib2to3": ["*.txt"], "black": ["py.typed"]}, - python_requires=">=3.6", - zip_safe=False, - install_requires=[ - "click>=7.1.2", - "appdirs", - "toml>=0.10.1", - "typed-ast>=1.4.0", - "regex>=2020.1.8", - "pathspec>=0.6, <1", - "dataclasses>=0.6; python_version < '3.7'", - "typing_extensions>=3.7.4", - "mypy_extensions>=0.4.3", - ], - extras_require={ - "d": ["aiohttp>=3.3.2", "aiohttp-cors"], - "colorama": ["colorama>=0.4.3"], - }, - test_suite="tests.test_black", - classifiers=[ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3 :: Only", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Software Development :: Quality Assurance", - ], - entry_points={ - "console_scripts": [ - "black=black:patched_main", - "blackd=blackd:patched_main [d]", - "black-primer=black_primer.cli:main", - ] - }, -) diff --git a/src/black/__init__.py b/src/black/__init__.py index 5b8c9749119..94592278c31 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -61,7 +61,7 @@ unmask_cell, ) from black.linegen import LN, LineGenerator, transform_line -from black.lines import EmptyLineTracker, Line +from black.lines import EmptyLineTracker, LinesBlock from black.mode import ( FUTURE_FLAG_TO_FEATURE, VERSION_TO_FEATURES, @@ -248,6 +248,12 @@ def validate_regex( ), default=[], ) +@click.option( + "-x", + "--skip-source-first-line", + is_flag=True, + help="Skip the first line of the source code.", +) @click.option( "-S", "--skip-string-normalization", @@ -428,6 +434,7 @@ def main( # noqa: C901 pyi: bool, ipynb: bool, python_cell_magics: Sequence[str], + skip_source_first_line: bool, skip_string_normalization: bool, skip_magic_trailing_comma: bool, experimental_string_processing: bool, @@ -490,8 +497,10 @@ def main( # noqa: C901 user_level_config = str(find_user_pyproject_toml()) if config == user_level_config: out( - "Using configuration from user-level config at " - f"'{user_level_config}'.", + ( + "Using configuration from user-level config at " + f"'{user_level_config}'." + ), fg="blue", ) elif config_source in ( @@ -528,6 +537,7 @@ def main( # noqa: C901 line_length=line_length, is_pyi=pyi, is_ipynb=ipynb, + skip_source_first_line=skip_source_first_line, string_normalization=not skip_string_normalization, magic_trailing_comma=not skip_magic_trailing_comma, experimental_string_processing=experimental_string_processing, @@ -790,7 +800,10 @@ def format_file_in_place( mode = replace(mode, is_ipynb=True) then = datetime.utcfromtimestamp(src.stat().st_mtime) + header = b"" with open(src, "rb") as buf: + if mode.skip_source_first_line: + header = buf.readline() src_contents, encoding, newline = decode_bytes(buf.read()) try: dst_contents = format_file_contents(src_contents, fast=fast, mode=mode) @@ -800,6 +813,8 @@ def format_file_in_place( raise ValueError( f"File '{src}' cannot be parsed as valid Jupyter notebook." ) from None + src_contents = header.decode(encoding) + src_contents + dst_contents = header.decode(encoding) + dst_contents if write_back == WriteBack.YES: with open(src, "w", encoding=encoding, newline=newline) as f: @@ -1062,7 +1077,7 @@ def f( def _format_str_once(src_contents: str, *, mode: Mode) -> str: src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions) - dst_contents = [] + dst_blocks: List[LinesBlock] = [] if mode.target_versions: versions = mode.target_versions else: @@ -1071,22 +1086,25 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str: normalize_fmt_off(src_node, preview=mode.preview) lines = LineGenerator(mode=mode) - elt = EmptyLineTracker(is_pyi=mode.is_pyi) - empty_line = Line(mode=mode) - after = 0 + elt = EmptyLineTracker(mode=mode) split_line_features = { feature for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF} if supports_feature(versions, feature) } + block: Optional[LinesBlock] = None for current_line in lines.visit(src_node): - dst_contents.append(str(empty_line) * after) - before, after = elt.maybe_empty_lines(current_line) - dst_contents.append(str(empty_line) * before) + block = elt.maybe_empty_lines(current_line) + dst_blocks.append(block) for line in transform_line( current_line, mode=mode, features=split_line_features ): - dst_contents.append(str(line)) + block.content_lines.append(str(line)) + if dst_blocks: + dst_blocks[-1].after = 0 + dst_contents = [] + for block in dst_blocks: + dst_contents.extend(block.all_lines()) return "".join(dst_contents) @@ -1369,9 +1387,9 @@ def patch_click() -> None: for module in modules: if hasattr(module, "_verify_python3_env"): - module._verify_python3_env = lambda: None # type: ignore + module._verify_python3_env = lambda: None if hasattr(module, "_verify_python_env"): - module._verify_python_env = lambda: None # type: ignore + module._verify_python_env = lambda: None def patched_main() -> None: diff --git a/src/black/concurrency.py b/src/black/concurrency.py index 10e288f4f93..1598f51e43f 100644 --- a/src/black/concurrency.py +++ b/src/black/concurrency.py @@ -47,12 +47,8 @@ def cancel(tasks: Iterable["asyncio.Task[Any]"]) -> None: def shutdown(loop: asyncio.AbstractEventLoop) -> None: """Cancel all pending tasks on `loop`, wait for them, and close the loop.""" try: - if sys.version_info[:2] >= (3, 7): - all_tasks = asyncio.all_tasks - else: - all_tasks = asyncio.Task.all_tasks # This part is borrowed from asyncio/runners.py in Python 3.7b2. - to_cancel = [task for task in all_tasks(loop) if not task.done()] + to_cancel = [task for task in asyncio.all_tasks(loop) if not task.done()] if not to_cancel: return diff --git a/src/black/lines.py b/src/black/lines.py index dd11dde7b55..9cda559c4ee 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -1,29 +1,38 @@ import itertools import sys from dataclasses import dataclass, field -from typing import Callable, Dict, Iterator, List, Optional, Sequence, Tuple, TypeVar, cast +from typing import ( + Callable, + Dict, + Iterator, + List, + Optional, + Sequence, + Tuple, + TypeVar, + cast, +) from black.brackets import DOT_PRIORITY, BracketTracker from black.mode import Mode, Preview from black.nodes import ( - BRACKETS, - CLOSING_BRACKETS, - OPENING_BRACKETS, - STANDALONE_COMMENT, - TEST_DESCENDANTS, - child_towards, - is_import, - is_multiline_string, - is_one_sequence_between, - is_type_comment, - replace_child, - syms, - whitespace, + BRACKETS, + CLOSING_BRACKETS, + OPENING_BRACKETS, + STANDALONE_COMMENT, + TEST_DESCENDANTS, + child_towards, + is_import, + is_multiline_string, + is_one_sequence_between, + is_type_comment, + replace_child, + syms, + whitespace, ) from blib2to3.pgen2 import token from blib2to3.pytree import Leaf, Node - # types T = TypeVar("T") Index = int @@ -439,6 +448,28 @@ def __bool__(self) -> bool: return bool(self.leaves or self.comments) +@dataclass +class LinesBlock: + """Class that holds information about a block of formatted lines. + + This is introduced so that the EmptyLineTracker can look behind the standalone + comments and adjust their empty lines for class or def lines. + """ + + mode: Mode + previous_block: Optional["LinesBlock"] + original_line: Line + before: int = 0 + content_lines: List[str] = field(default_factory=list) + after: int = 0 + + def all_lines(self) -> List[str]: + empty_line = str(Line(mode=self.mode)) + return ( + [empty_line * self.before] + self.content_lines + [empty_line * self.after] + ) + + @dataclass class EmptyLineTracker: """Provides a stateful method that returns the number of potential extra @@ -449,33 +480,55 @@ class EmptyLineTracker: are consumed by `maybe_empty_lines()` and included in the computation. """ - is_pyi: bool = False + mode: Mode previous_line: Optional[Line] = None - previous_after: int = 0 + previous_block: Optional[LinesBlock] = None previous_defs: List[int] = field(default_factory=list) + semantic_leading_comment: Optional[LinesBlock] = None - def maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: + def maybe_empty_lines(self, current_line: Line) -> LinesBlock: """Return the number of extra empty lines before and after the `current_line`. This is for separating `def`, `async def` and `class` with extra empty lines (two on module-level). """ before, after = self._maybe_empty_lines(current_line) + previous_after = self.previous_block.after if self.previous_block else 0 before = ( # Black should not insert empty lines at the beginning # of the file 0 if self.previous_line is None - else before - self.previous_after + else before - previous_after ) - self.previous_after = after + block = LinesBlock( + mode=self.mode, + previous_block=self.previous_block, + original_line=current_line, + before=before, + after=after, + ) + + # Maintain the semantic_leading_comment state. + if current_line.is_comment: + if self.previous_line is None or ( + not self.previous_line.is_decorator + # `or before` means this comment already has an empty line before + and (not self.previous_line.is_comment or before) + and (self.semantic_leading_comment is None or before) + ): + self.semantic_leading_comment = block + elif not current_line.is_decorator: + self.semantic_leading_comment = None + self.previous_line = current_line - return before, after + self.previous_block = block + return block def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: max_allowed = 1 if current_line.depth == 0: - max_allowed = 1 if self.is_pyi else 2 + max_allowed = 1 if self.mode.is_pyi else 2 if current_line.leaves: # Consume the first leaf's extra newlines. first_leaf = current_line.leaves[0] @@ -486,7 +539,7 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]: before = 0 depth = current_line.depth while self.previous_defs and self.previous_defs[-1] >= depth: - if self.is_pyi: + if self.mode.is_pyi: assert self.previous_line is not None if depth and not current_line.is_def and self.previous_line.is_def: # Empty lines between attributes and methods should be preserved. @@ -554,7 +607,7 @@ def _maybe_empty_lines_for_class_or_def( return 0, 0 if self.previous_line.is_decorator: - if self.is_pyi and current_line.is_stub_class: + if self.mode.is_pyi and current_line.is_stub_class: # Insert an empty line after a decorated stub class return 0, 1 @@ -565,14 +618,27 @@ def _maybe_empty_lines_for_class_or_def( ): return 0, 0 + comment_to_add_newlines: Optional[LinesBlock] = None if ( self.previous_line.is_comment and self.previous_line.depth == current_line.depth and before == 0 ): - return 0, 0 + slc = self.semantic_leading_comment + if ( + Preview.empty_lines_before_class_or_def_with_leading_comments + in current_line.mode + and slc is not None + and slc.previous_block is not None + and not slc.previous_block.original_line.is_class + and not slc.previous_block.original_line.opens_block + and slc.before <= 1 + ): + comment_to_add_newlines = slc + else: + return 0, 0 - if self.is_pyi: + if self.mode.is_pyi: if current_line.is_class or self.previous_line.is_class: if self.previous_line.depth < current_line.depth: newlines = 0 @@ -600,6 +666,13 @@ def _maybe_empty_lines_for_class_or_def( newlines = 0 else: newlines = 1 if current_line.depth else 2 + if comment_to_add_newlines is not None: + previous_block = comment_to_add_newlines.previous_block + if previous_block is not None: + comment_to_add_newlines.before = ( + max(comment_to_add_newlines.before, newlines) - previous_block.after + ) + newlines = 0 return newlines, 0 diff --git a/src/black/mode.py b/src/black/mode.py index 6c0847e8bcc..e2eff2391b1 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -150,6 +150,7 @@ class Preview(Enum): """Individual preview style features.""" annotation_parens = auto() + empty_lines_before_class_or_def_with_leading_comments = auto() long_docstring_quotes_on_newline = auto() normalize_docstring_quotes_and_prefixes_properly = auto() one_element_subscript = auto() @@ -170,6 +171,7 @@ class Mode: string_normalization: bool = True is_pyi: bool = False is_ipynb: bool = False + skip_source_first_line: bool = False magic_trailing_comma: bool = True experimental_string_processing: bool = False python_cell_magics: Set[str] = field(default_factory=set) @@ -178,8 +180,10 @@ class Mode: def __post_init__(self) -> None: if self.experimental_string_processing: warn( - "`experimental string processing` has been included in `preview`" - " and deprecated. Use `preview` instead.", + ( + "`experimental string processing` has been included in `preview`" + " and deprecated. Use `preview` instead." + ), Deprecated, ) @@ -208,6 +212,7 @@ def get_cache_key(self) -> str: str(int(self.string_normalization)), str(int(self.is_pyi)), str(int(self.is_ipynb)), + str(int(self.skip_source_first_line)), str(int(self.magic_trailing_comma)), str(int(self.experimental_string_processing)), str(int(self.preview)), diff --git a/src/black/parsing.py b/src/black/parsing.py index 64c0b1e3018..c37c12b868d 100644 --- a/src/black/parsing.py +++ b/src/black/parsing.py @@ -27,12 +27,13 @@ try: from typed_ast import ast3 except ImportError: - # Either our python version is too low, or we're on pypy - if sys.version_info < (3, 7) or (sys.version_info < (3, 8) and not _IS_PYPY): + if sys.version_info < (3, 8) and not _IS_PYPY: print( - "The typed_ast package is required but not installed.\n" - "You can upgrade to Python 3.8+ or install typed_ast with\n" - "`python3 -m pip install typed-ast`.", + ( + "The typed_ast package is required but not installed.\n" + "You can upgrade to Python 3.8+ or install typed_ast with\n" + "`python3 -m pip install typed-ast`." + ), file=sys.stderr, ) sys.exit(1) @@ -166,8 +167,6 @@ def parse_single_version( # comments separately. return ast3.parse(src, filename, feature_version=version[1]) - raise AssertionError("INTERNAL ERROR: Tried parsing unsupported Python version!") - def parse_ast(src: str) -> Union[ast.AST, ast3.AST]: # TODO: support Python 4+ ;) diff --git a/src/black/trans.py b/src/black/trans.py index 7e2d8e67c1a..8893ab02aab 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -1058,33 +1058,19 @@ def _prefer_paren_wrap_match(LL: List[Leaf]) -> Optional[int]: if LL[0].type != token.STRING: return None - matching_nodes = [ - syms.listmaker, - syms.dictsetmaker, - syms.testlist_gexp, - ] - # If the string is an immediate child of a list/set/tuple literal... - if ( - parent_type(LL[0]) in matching_nodes - or parent_type(LL[0].parent) in matching_nodes + # If the string is surrounded by commas (or is the first/last child)... + prev_sibling = LL[0].prev_sibling + next_sibling = LL[0].next_sibling + if not prev_sibling and not next_sibling and parent_type(LL[0]) == syms.atom: + # If it's an atom string, we need to check the parent atom's siblings. + parent = LL[0].parent + assert parent is not None # For type checkers. + prev_sibling = parent.prev_sibling + next_sibling = parent.next_sibling + if (not prev_sibling or prev_sibling.type == token.COMMA) and ( + not next_sibling or next_sibling.type == token.COMMA ): - # And the string is surrounded by commas (or is the first/last child)... - prev_sibling = LL[0].prev_sibling - next_sibling = LL[0].next_sibling - if ( - not prev_sibling - and not next_sibling - and parent_type(LL[0]) == syms.atom - ): - # If it's an atom string, we need to check the parent atom's siblings. - parent = LL[0].parent - assert parent is not None # For type checkers. - prev_sibling = parent.prev_sibling - next_sibling = parent.next_sibling - if (not prev_sibling or prev_sibling.type == token.COMMA) and ( - not next_sibling or next_sibling.type == token.COMMA - ): - return 0 + return 0 return None @@ -1653,9 +1639,8 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin): assigned the value of some string. OR * The line starts with an "atom" string that prefers to be wrapped in - parens. It's preferred to be wrapped when it's is an immediate child of - a list/set/tuple literal, AND the string is surrounded by commas (or is - the first/last child). + parens. It's preferred to be wrapped when the string is surrounded by + commas (or is the first/last child). Transformations: The chosen string is wrapped in parentheses and then split at the LPAR. diff --git a/src/black_primer/primer.json b/src/black_primer/primer.json deleted file mode 100644 index 546f47782cd..00000000000 --- a/src/black_primer/primer.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "configuration_format_version": 20200509, - "projects": { - "aioexabgp": { - "cli_arguments": [], - "expect_formatting_changes": false, - "git_clone_url": "https://github.com/cooperlees/aioexabgp.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "attrs": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/python-attrs/attrs.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "bandersnatch": { - "cli_arguments": [], - "expect_formatting_changes": false, - "git_clone_url": "https://github.com/pypa/bandersnatch.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "channels": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/django/channels.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "django": { - "disabled_reason": "black --check --diff returned 123 on two files", - "disabled": true, - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/django/django.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "flake8-bugbear": { - "cli_arguments": [], - "expect_formatting_changes": false, - "git_clone_url": "https://github.com/PyCQA/flake8-bugbear.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "hypothesis": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/HypothesisWorks/hypothesis.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "pandas": { - "disabled_reason": "black --check --diff returned 123 on one file", - "disabled": true, - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/pandas-dev/pandas.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "poetry": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/python-poetry/poetry.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "pyramid": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/Pylons/pyramid.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "ptr": { - "cli_arguments": [], - "expect_formatting_changes": false, - "git_clone_url": "https://github.com/facebookincubator/ptr.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "pytest": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/pytest-dev/pytest.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "sqlalchemy": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/sqlalchemy/sqlalchemy.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "tox": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/tox-dev/tox.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "virtualenv": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/pypa/virtualenv.git", - "long_checkout": false, - "py_versions": ["all"] - }, - "warehouse": { - "cli_arguments": [], - "expect_formatting_changes": true, - "git_clone_url": "https://github.com/pypa/warehouse.git", - "long_checkout": false, - "py_versions": ["all"] - } - } -} diff --git a/src/blackd/__init__.py b/src/blackd/__init__.py index 6bbc7c52086..ba4750b8298 100644 --- a/src/blackd/__init__.py +++ b/src/blackd/__init__.py @@ -30,6 +30,7 @@ PROTOCOL_VERSION_HEADER = "X-Protocol-Version" LINE_LENGTH_HEADER = "X-Line-Length" PYTHON_VARIANT_HEADER = "X-Python-Variant" +SKIP_SOURCE_FIRST_LINE = "X-Skip-Source-First-Line" SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization" SKIP_MAGIC_TRAILING_COMMA = "X-Skip-Magic-Trailing-Comma" PREVIEW = "X-Preview" @@ -40,6 +41,7 @@ PROTOCOL_VERSION_HEADER, LINE_LENGTH_HEADER, PYTHON_VARIANT_HEADER, + SKIP_SOURCE_FIRST_LINE, SKIP_STRING_NORMALIZATION_HEADER, SKIP_MAGIC_TRAILING_COMMA, PREVIEW, @@ -111,6 +113,9 @@ async def handle(request: web.Request, executor: Executor) -> web.Response: skip_magic_trailing_comma = bool( request.headers.get(SKIP_MAGIC_TRAILING_COMMA, False) ) + skip_source_first_line = bool( + request.headers.get(SKIP_SOURCE_FIRST_LINE, False) + ) preview = bool(request.headers.get(PREVIEW, False)) fast = False if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast": @@ -119,6 +124,7 @@ async def handle(request: web.Request, executor: Executor) -> web.Response: target_versions=versions, is_pyi=pyi, line_length=line_length, + skip_source_first_line=skip_source_first_line, string_normalization=not skip_string_normalization, magic_trailing_comma=not skip_magic_trailing_comma, preview=preview, @@ -128,6 +134,12 @@ async def handle(request: web.Request, executor: Executor) -> web.Response: req_str = req_bytes.decode(charset) then = datetime.utcnow() + header = "" + if skip_source_first_line: + first_newline_position: int = req_str.find("\n") + 1 + header = req_str[:first_newline_position] + req_str = req_str[first_newline_position:] + loop = asyncio.get_event_loop() formatted_str = await loop.run_in_executor( executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode) @@ -140,6 +152,10 @@ async def handle(request: web.Request, executor: Executor) -> web.Response: if formatted_str == req_str: raise black.NothingChanged + # Put the source first line back + req_str = header + req_str + formatted_str = header + formatted_str + # Only output the diff in the HTTP response only_diff = bool(request.headers.get(DIFF_HEADER, False)) if only_diff: diff --git a/src/blib2to3/Grammar.txt b/src/blib2to3/Grammar.txt index ac7ad7643ff..bd8a452a386 100644 --- a/src/blib2to3/Grammar.txt +++ b/src/blib2to3/Grammar.txt @@ -186,7 +186,7 @@ arglist: argument (',' argument)* [','] # multiple (test comp_for) arguments are blocked; keyword unpackings # that precede iterable unpackings are blocked; etc. argument: ( test [comp_for] | - test ':=' test | + test ':=' test [comp_for] | test 'as' test | test '=' asexpr_test | '**' test | diff --git a/test_requirements.txt b/test_requirements.txt index 5bc494d5999..ef61a1210ee 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,6 +1,6 @@ coverage >= 5.3 pre-commit pytest >= 6.1.1 -pytest-xdist >= 2.2.1 +pytest-xdist >= 2.2.1, < 3.0.2 pytest-cov >= 2.11.1 tox diff --git a/tests/data/comments7.py b/tests/data/comments7.py deleted file mode 100644 index 0e2bd35bf55..00000000000 --- a/tests/data/comments7.py +++ /dev/null @@ -1,266 +0,0 @@ -from .config import ( - Any, - Bool, - ConfigType, - ConfigTypeAttributes, - Int, - Path, - # String, - # resolve_to_config_type, - # DEFAULT_TYPE_ATTRIBUTES, -) - - -from .config import ( - Any, - Bool, - ConfigType, - ConfigTypeAttributes, - Int, - no_comma_here_yet - # and some comments, - # resolve_to_config_type, - # DEFAULT_TYPE_ATTRIBUTES, -) -from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( - MyLovelyCompanyTeamProjectComponent # NOT DRY -) -from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( - MyLovelyCompanyTeamProjectComponent as component # DRY -) - - -result = 1 # look ma, no comment migration xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - -result = ( - 1 # look ma, no comment migration xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -) - -result = ( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # aaa -) - -result = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # aaa - - -def func(): - c = call( - 0.0123, - 0.0456, - 0.0789, - 0.0123, - 0.0789, - a[-1], # type: ignore - ) - c = call( - 0.0123, - 0.0456, - 0.0789, - 0.0123, - 0.0789, - a[-1] # type: ignore - ) - c = call( - 0.0123, - 0.0456, - 0.0789, - 0.0123, - 0.0456, - 0.0789, - 0.0123, - 0.0456, - 0.0789, - a[-1] # type: ignore - ) - - # The type: ignore exception only applies to line length, not - # other types of formatting. - c = call( - "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", # type: ignore - "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa" - ) - - -class C: - @pytest.mark.parametrize( - ("post_data", "message"), - [ - # metadata_version errors. - ( - {}, - "None is an invalid value for Metadata-Version. Error: This field is" - " required. see" - " https://packaging.python.org/specifications/core-metadata" - ), - ( - {"metadata_version": "-1"}, - "'-1' is an invalid value for Metadata-Version. Error: Unknown Metadata" - " Version see" - " https://packaging.python.org/specifications/core-metadata" - ), - # name errors. - ( - {"metadata_version": "1.2"}, - "'' is an invalid value for Name. Error: This field is required. see" - " https://packaging.python.org/specifications/core-metadata" - ), - ( - {"metadata_version": "1.2", "name": "foo-"}, - "'foo-' is an invalid value for Name. Error: Must start and end with a" - " letter or numeral and contain only ascii numeric and '.', '_' and" - " '-'. see https://packaging.python.org/specifications/core-metadata" - ), - # version errors. - ( - {"metadata_version": "1.2", "name": "example"}, - "'' is an invalid value for Version. Error: This field is required. see" - " https://packaging.python.org/specifications/core-metadata" - ), - ( - {"metadata_version": "1.2", "name": "example", "version": "dog"}, - "'dog' is an invalid value for Version. Error: Must start and end with" - " a letter or numeral and contain only ascii numeric and '.', '_' and" - " '-'. see https://packaging.python.org/specifications/core-metadata" - ) - ] - ) - def test_fails_invalid_post_data( - self, pyramid_config, db_request, post_data, message - ): - ... - -# output - -from .config import ( - Any, - Bool, - ConfigType, - ConfigTypeAttributes, - Int, - Path, - # String, - # resolve_to_config_type, - # DEFAULT_TYPE_ATTRIBUTES, -) - - -from .config import ( - Any, - Bool, - ConfigType, - ConfigTypeAttributes, - Int, - no_comma_here_yet, - # and some comments, - # resolve_to_config_type, - # DEFAULT_TYPE_ATTRIBUTES, -) -from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( - MyLovelyCompanyTeamProjectComponent, # NOT DRY -) -from com.my_lovely_company.my_lovely_team.my_lovely_project.my_lovely_component import ( - MyLovelyCompanyTeamProjectComponent as component, # DRY -) - - -result = 1 # look ma, no comment migration xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - -result = 1 # look ma, no comment migration xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - -result = ( # aaa - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -) - -result = ( # aaa - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -) - - -def func(): - c = call( - 0.0123, - 0.0456, - 0.0789, - 0.0123, - 0.0789, - a[-1], # type: ignore - ) - c = call(0.0123, 0.0456, 0.0789, 0.0123, 0.0789, a[-1]) # type: ignore - c = call( - 0.0123, - 0.0456, - 0.0789, - 0.0123, - 0.0456, - 0.0789, - 0.0123, - 0.0456, - 0.0789, - a[-1], # type: ignore - ) - - # The type: ignore exception only applies to line length, not - # other types of formatting. - c = call( - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", # type: ignore - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", - "aaaaaaaa", - ) - - -class C: - @pytest.mark.parametrize( - ("post_data", "message"), - [ - # metadata_version errors. - ( - {}, - "None is an invalid value for Metadata-Version. Error: This field is" - " required. see" - " https://packaging.python.org/specifications/core-metadata", - ), - ( - {"metadata_version": "-1"}, - "'-1' is an invalid value for Metadata-Version. Error: Unknown Metadata" - " Version see" - " https://packaging.python.org/specifications/core-metadata", - ), - # name errors. - ( - {"metadata_version": "1.2"}, - "'' is an invalid value for Name. Error: This field is required. see" - " https://packaging.python.org/specifications/core-metadata", - ), - ( - {"metadata_version": "1.2", "name": "foo-"}, - "'foo-' is an invalid value for Name. Error: Must start and end with a" - " letter or numeral and contain only ascii numeric and '.', '_' and" - " '-'. see https://packaging.python.org/specifications/core-metadata", - ), - # version errors. - ( - {"metadata_version": "1.2", "name": "example"}, - "'' is an invalid value for Version. Error: This field is required. see" - " https://packaging.python.org/specifications/core-metadata", - ), - ( - {"metadata_version": "1.2", "name": "example", "version": "dog"}, - "'dog' is an invalid value for Version. Error: Must start and end with" - " a letter or numeral and contain only ascii numeric and '.', '_' and" - " '-'. see https://packaging.python.org/specifications/core-metadata", - ), - ], - ) - def test_fails_invalid_post_data( - self, pyramid_config, db_request, post_data, message - ): - ... \ No newline at end of file diff --git a/tests/data/composition_no_trailing_comma.py b/tests/data/composition_no_trailing_comma.py deleted file mode 100644 index f17b89dea8d..00000000000 --- a/tests/data/composition_no_trailing_comma.py +++ /dev/null @@ -1,367 +0,0 @@ -class C: - def test(self) -> None: - with patch("black.out", print): - self.assertEqual( - unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." - ) - self.assertEqual( - unstyle(str(report)), - "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", - ) - self.assertEqual( - unstyle(str(report)), - "2 files reformatted, 1 file left unchanged, 1 file failed to" - " reformat.", - ) - self.assertEqual( - unstyle(str(report)), - "2 files reformatted, 2 files left unchanged, 2 files failed to" - " reformat.", - ) - for i in (a,): - if ( - # Rule 1 - i % 2 == 0 - # Rule 2 - and i % 3 == 0 - ): - while ( - # Just a comment - call() - # Another - ): - print(i) - xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( - push_manager=context.request.resource_manager, - max_items_to_push=num_items, - batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE - ).push( - # Only send the first n items. - items=items[:num_items] - ) - return ( - 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' - % (test.name, test.filename, lineno, lname, err) - ) - - def omitting_trailers(self) -> None: - get_collection( - hey_this_is_a_very_long_call, it_has_funny_attributes, really=True - )[OneLevelIndex] - get_collection( - hey_this_is_a_very_long_call, it_has_funny_attributes, really=True - )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] - d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ - 22 - ] - assignment = ( - some.rather.elaborate.rule() and another.rule.ending_with.index[123] - ) - - def easy_asserts(self) -> None: - assert { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - } == expected, "Not what we expected" - - assert expected == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - }, "Not what we expected" - - assert expected == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - } - - def tricky_asserts(self) -> None: - assert { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - } == expected( - value, is_going_to_be="too long to fit in a single line", srsly=True - ), "Not what we expected" - - assert { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - } == expected, ( - "Not what we expected and the message is too long to fit in one line" - ) - - assert expected( - value, is_going_to_be="too long to fit in a single line", srsly=True - ) == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - }, "Not what we expected" - - assert expected == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - }, ( - "Not what we expected and the message is too long to fit in one line" - " because it's too long" - ) - - dis_c_instance_method = """\ - %3d 0 LOAD_FAST 1 (x) - 2 LOAD_CONST 1 (1) - 4 COMPARE_OP 2 (==) - 6 LOAD_FAST 0 (self) - 8 STORE_ATTR 0 (x) - 10 LOAD_CONST 0 (None) - 12 RETURN_VALUE - """ % ( - _C.__init__.__code__.co_firstlineno + 1, - ) - - assert ( - expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect - == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9 - } - ) - - - -# output - -class C: - def test(self) -> None: - with patch("black.out", print): - self.assertEqual( - unstyle(str(report)), "1 file reformatted, 1 file failed to reformat." - ) - self.assertEqual( - unstyle(str(report)), - "1 file reformatted, 1 file left unchanged, 1 file failed to reformat.", - ) - self.assertEqual( - unstyle(str(report)), - "2 files reformatted, 1 file left unchanged, 1 file failed to" - " reformat.", - ) - self.assertEqual( - unstyle(str(report)), - "2 files reformatted, 2 files left unchanged, 2 files failed to" - " reformat.", - ) - for i in (a,): - if ( - # Rule 1 - i % 2 == 0 - # Rule 2 - and i % 3 == 0 - ): - while ( - # Just a comment - call() - # Another - ): - print(i) - xxxxxxxxxxxxxxxx = Yyyy2YyyyyYyyyyy( - push_manager=context.request.resource_manager, - max_items_to_push=num_items, - batch_size=Yyyy2YyyyYyyyyYyyy.FULL_SIZE, - ).push( - # Only send the first n items. - items=items[:num_items] - ) - return ( - 'Utterly failed doctest test for %s\n File "%s", line %s, in %s\n\n%s' - % (test.name, test.filename, lineno, lname, err) - ) - - def omitting_trailers(self) -> None: - get_collection( - hey_this_is_a_very_long_call, it_has_funny_attributes, really=True - )[OneLevelIndex] - get_collection( - hey_this_is_a_very_long_call, it_has_funny_attributes, really=True - )[OneLevelIndex][TwoLevelIndex][ThreeLevelIndex][FourLevelIndex] - d[0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][ - 22 - ] - assignment = ( - some.rather.elaborate.rule() and another.rule.ending_with.index[123] - ) - - def easy_asserts(self) -> None: - assert { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - } == expected, "Not what we expected" - - assert expected == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - }, "Not what we expected" - - assert expected == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - } - - def tricky_asserts(self) -> None: - assert { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - } == expected( - value, is_going_to_be="too long to fit in a single line", srsly=True - ), "Not what we expected" - - assert { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - } == expected, ( - "Not what we expected and the message is too long to fit in one line" - ) - - assert expected( - value, is_going_to_be="too long to fit in a single line", srsly=True - ) == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - }, "Not what we expected" - - assert expected == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - }, ( - "Not what we expected and the message is too long to fit in one line" - " because it's too long" - ) - - dis_c_instance_method = """\ - %3d 0 LOAD_FAST 1 (x) - 2 LOAD_CONST 1 (1) - 4 COMPARE_OP 2 (==) - 6 LOAD_FAST 0 (self) - 8 STORE_ATTR 0 (x) - 10 LOAD_CONST 0 (None) - 12 RETURN_VALUE - """ % ( - _C.__init__.__code__.co_firstlineno + 1, - ) - - assert ( - expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect - == { - key1: value1, - key2: value2, - key3: value3, - key4: value4, - key5: value5, - key6: value6, - key7: value7, - key8: value8, - key9: value9, - } - ) diff --git a/tests/data/docstring.py b/tests/data/docstring.py deleted file mode 100644 index 2d3d73a101c..00000000000 --- a/tests/data/docstring.py +++ /dev/null @@ -1,225 +0,0 @@ -class MyClass: - """ Multiline - class docstring - """ - - def method(self): - """Multiline - method docstring - """ - pass - - -def foo(): - """This is a docstring with - some lines of text here - """ - return - - -def bar(): - '''This is another docstring - with more lines of text - ''' - return - - -def baz(): - '''"This" is a string with some - embedded "quotes"''' - return - - -def troz(): - '''Indentation with tabs - is just as OK - ''' - return - - -def zort(): - """Another - multiline - docstring - """ - pass - -def poit(): - """ - Lorem ipsum dolor sit amet. - - Consectetur adipiscing elit: - - sed do eiusmod tempor incididunt ut labore - - dolore magna aliqua - - enim ad minim veniam - - quis nostrud exercitation ullamco laboris nisi - - aliquip ex ea commodo consequat - """ - pass - - -def under_indent(): - """ - These lines are indented in a way that does not -make sense. - """ - pass - - -def over_indent(): - """ - This has a shallow indent - - But some lines are deeper - - And the closing quote is too deep - """ - pass - - -def single_line(): - """But with a newline after it! - - """ - pass - - -def this(): - r""" - 'hey ho' - """ - - -def that(): - """ "hey yah" """ - - -def and_that(): - """ - "hey yah" """ - - -def and_this(): - ''' - "hey yah"''' - - -def believe_it_or_not_this_is_in_the_py_stdlib(): ''' -"hey yah"''' - - -def ignored_docstring(): - """a => \ -b""" - -# output - -class MyClass: - """Multiline - class docstring - """ - - def method(self): - """Multiline - method docstring - """ - pass - - -def foo(): - """This is a docstring with - some lines of text here - """ - return - - -def bar(): - """This is another docstring - with more lines of text - """ - return - - -def baz(): - '''"This" is a string with some - embedded "quotes"''' - return - - -def troz(): - """Indentation with tabs - is just as OK - """ - return - - -def zort(): - """Another - multiline - docstring - """ - pass - - -def poit(): - """ - Lorem ipsum dolor sit amet. - - Consectetur adipiscing elit: - - sed do eiusmod tempor incididunt ut labore - - dolore magna aliqua - - enim ad minim veniam - - quis nostrud exercitation ullamco laboris nisi - - aliquip ex ea commodo consequat - """ - pass - - -def under_indent(): - """ - These lines are indented in a way that does not - make sense. - """ - pass - - -def over_indent(): - """ - This has a shallow indent - - But some lines are deeper - - And the closing quote is too deep - """ - pass - - -def single_line(): - """But with a newline after it!""" - pass - - -def this(): - r""" - 'hey ho' - """ - - -def that(): - """ "hey yah" """ - - -def and_that(): - """ - "hey yah" """ - - -def and_this(): - ''' - "hey yah"''' - - -def believe_it_or_not_this_is_in_the_py_stdlib(): - ''' - "hey yah"''' - - -def ignored_docstring(): - """a => \ -b""" \ No newline at end of file diff --git a/tests/data/function_trailing_comma.py b/tests/data/function_trailing_comma.py deleted file mode 100644 index d15459cbeb5..00000000000 --- a/tests/data/function_trailing_comma.py +++ /dev/null @@ -1,88 +0,0 @@ -def f(a,): - d = {'key': 'value',} - tup = (1,) - -def f2(a,b,): - d = {'key': 'value', 'key2': 'value2',} - tup = (1,2,) - -def f(a:int=1,): - call(arg={'explode': 'this',}) - call2(arg=[1,2,3],) - x = { - "a": 1, - "b": 2, - }["a"] - if a == {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]: - pass - -def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -]: - json = {"k": {"k2": {"k3": [1,]}}} - -# output - -def f( - a, -): - d = { - "key": "value", - } - tup = (1,) - - -def f2( - a, - b, -): - d = { - "key": "value", - "key2": "value2", - } - tup = ( - 1, - 2, - ) - - -def f( - a: int = 1, -): - call( - arg={ - "explode": "this", - } - ) - call2( - arg=[1, 2, 3], - ) - x = { - "a": 1, - "b": 2, - }["a"] - if a == { - "a": 1, - "b": 2, - "c": 3, - "d": 4, - "e": 5, - "f": 6, - "g": 7, - "h": 8, - }["a"]: - pass - - -def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -]: - json = { - "k": { - "k2": { - "k3": [ - 1, - ] - } - } - } \ No newline at end of file diff --git a/tests/data/long_strings_flag_disabled.py b/tests/data/long_strings_flag_disabled.py deleted file mode 100644 index ef3094fd779..00000000000 --- a/tests/data/long_strings_flag_disabled.py +++ /dev/null @@ -1,289 +0,0 @@ -x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -y = "Short string" - -print( - "This is a really long string inside of a print statement with extra arguments attached at the end of it.", - x, - y, - z, -) - -print( - "This is a really long string inside of a print statement with no extra arguments attached at the end of it." -) - -D1 = { - "The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D2 = { - 1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D3 = { - x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", - y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary.", -} - -D4 = { - "A long and ridiculous {}".format( - string_key - ): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", - some_func( - "calling", "some", "stuff" - ): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format( - sooo="soooo", x=2 - ), - "A %s %s" - % ( - "formatted", - "string", - ): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." - % ("soooo", 2), -} - -func_with_keywords( - my_arg, - my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.", -) - -bad_split1 = ( - "But what should happen when code has already been formatted but in the wrong way? Like" - " with a space at the end instead of the beginning. Or what about when it is split too soon?" -) - -bad_split2 = ( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." -) - -bad_split3 = ( - "What if we have inline comments on " # First Comment - "each line of a bad split? In that " # Second Comment - "case, we should just leave it alone." # Third Comment -) - -bad_split_func1( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split.", - xxx, - yyy, - zzz, -) - -bad_split_func2( - xxx, - yyy, - zzz, - long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " - "with a space at the end instead of the beginning. Or what about when it is split too " - "soon?", -) - -bad_split_func3( - ( - "But what should happen when code has already " - r"been formatted but in the wrong way? Like " - "with a space at the end instead of the " - r"beginning. Or what about when it is split too " - r"soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." - ), - xxx, - yyy, - zzz, -) - -raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." - -fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format( - "method calls" -) - -fmt_string2 = "But what about when the string is {} but {}".format( - "short", - "the method call is really really really really really really really really long?", -) - -old_fmt_string1 = ( - "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." - % ("formatting", "code") -) - -old_fmt_string2 = "This is a %s %s %s %s" % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", -) - -old_fmt_string3 = "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" % ( - "really really really really really", - "old", - "way to format strings!", - "Use f-strings instead!", -) - -fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." - -fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." - -comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. - -arg_comment_string = print( - "Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment stays on the bottom. - "Arg #2", - "Arg #3", - "Arg #4", - "Arg #5", -) - -pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 - -pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa - -"""This is a really really really long triple quote string and it should not be touched.""" - -triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" - -assert ( - some_type_of_boolean_expression -), "Followed by a really really really long string that is used to provide context to the AssertionError exception." - -assert ( - some_type_of_boolean_expression -), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( - "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." - % "formatting" -) - -assert some_type_of_boolean_expression, ( - "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." - % ("string", "formatting") -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string." -) - -some_function_call( - "With a reallly generic name and with a really really long string that is, at some point down the line, " - + added - + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", - "and a second argument", - and_a_third, -) - -return "A really really really really really really really really really really really really really long {} {}".format( - "return", "value" -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), # comment after comma -) - -func_with_bad_parens_that_wont_fit_in_one_line( - ("short string that should have parens stripped"), x, y, z -) - -func_with_bad_parens_that_wont_fit_in_one_line( - x, y, ("short string that should have parens stripped"), z -) - -func_with_bad_parens( - ("short string that should have parens stripped"), - x, - y, - z, -) - -func_with_bad_parens( - x, - y, - ("short string that should have parens stripped"), - z, -) - -annotated_variable: Final = ( - "This is a large " - + STRING - + " that has been " - + CONCATENATED - + "using the '+' operator." -) -annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." -annotated_variable: Literal[ - "fakse_literal" -] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." - -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" -backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" - -short_string = "Hi" " there." - -func_call(short_string=("Hi" " there.")) - -raw_strings = r"Don't" " get" r" merged" " unless they are all raw." - - -def foo(): - yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - - -x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check - " of it." -) diff --git a/tests/data/miscellaneous/invalid_header.py b/tests/data/miscellaneous/invalid_header.py new file mode 100644 index 00000000000..fb49e2f93e7 --- /dev/null +++ b/tests/data/miscellaneous/invalid_header.py @@ -0,0 +1,2 @@ +This is not valid Python syntax +y = "This is valid syntax" diff --git a/tests/data/percent_precedence.py b/tests/data/percent_precedence.py deleted file mode 100644 index b895443fb46..00000000000 --- a/tests/data/percent_precedence.py +++ /dev/null @@ -1,41 +0,0 @@ -("" % a) ** 2 -("" % a)[0] -("" % a)() -("" % a).b - -2 * ("" % a) -2 @ ("" % a) -2 / ("" % a) -2 // ("" % a) -2 % ("" % a) -+("" % a) -b + ("" % a) --("" % a) -b - ("" % a) -b + -("" % a) -~("" % a) -2 ** ("" % a) -await ("" % a) -b[("" % a)] -b(("" % a)) -# output -("" % a) ** 2 -("" % a)[0] -("" % a)() -("" % a).b - -2 * ("" % a) -2 @ ("" % a) -2 / ("" % a) -2 // ("" % a) -2 % ("" % a) -+("" % a) -b + "" % a --("" % a) -b - "" % a -b + -("" % a) -~("" % a) -2 ** ("" % a) -await ("" % a) -b[("" % a)] -b(("" % a)) diff --git a/tests/data/preview/cantfit.py b/tests/data/preview/cantfit.py index 0849374f776..cade382e30d 100644 --- a/tests/data/preview/cantfit.py +++ b/tests/data/preview/cantfit.py @@ -79,10 +79,14 @@ ) # long arguments normal_name = normal_function_name( - "but with super long string arguments that on their own exceed the line limit so" - " there's no way it can ever fit", - "eggs with spam and eggs and spam with eggs with spam and eggs and spam with eggs" - " with spam and eggs and spam with eggs", + ( + "but with super long string arguments that on their own exceed the line limit" + " so there's no way it can ever fit" + ), + ( + "eggs with spam and eggs and spam with eggs with spam and eggs and spam with" + " eggs with spam and eggs and spam with eggs" + ), this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it=0, ) string_variable_name = "a string that is waaaaaaaayyyyyyyy too long, even in parens, there's nothing you can do" # noqa diff --git a/tests/data/preview/comments9.py b/tests/data/preview/comments9.py new file mode 100644 index 00000000000..449612c037a --- /dev/null +++ b/tests/data/preview/comments9.py @@ -0,0 +1,254 @@ +# Test for https://github.com/psf/black/issues/246. + +some = statement +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +# leading 2 extra +@deco2(with_args=True) +# leading 3 +@deco3 +# leading 4 +def decorated(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + # Leading comment before inline function + def inline(): + pass + # Another leading comment + def another_inline(): + pass + else: + # More leading comments + def inline_after_else(): + pass + + +if a: + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + # Another leading comment + def another_top_level_quote_inline_inline(): + pass +else: + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass + + +# output + + +# Test for https://github.com/psf/black/issues/246. + +some = statement + + +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement + + +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" + + +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +# leading 2 extra +@deco2(with_args=True) +# leading 3 +@deco3 +# leading 4 +def decorated(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + # Leading comment before inline function + def inline(): + pass + + # Another leading comment + def another_inline(): + pass + + else: + # More leading comments + def inline_after_else(): + pass + + +if a: + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + + # Another leading comment + def another_top_level_quote_inline_inline(): + pass + +else: + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass diff --git a/tests/data/preview/long_strings.py b/tests/data/preview/long_strings.py index 6db3cfed9a9..9288b253b60 100644 --- a/tests/data/preview/long_strings.py +++ b/tests/data/preview/long_strings.py @@ -297,8 +297,10 @@ def foo(): y = "Short string" print( - "This is a really long string inside of a print statement with extra arguments" - " attached at the end of it.", + ( + "This is a really long string inside of a print statement with extra arguments" + " attached at the end of it." + ), x, y, z, @@ -474,13 +476,15 @@ def foo(): ) bad_split_func1( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split.", + ( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." + ), xxx, yyy, zzz, @@ -583,9 +587,11 @@ def foo(): ) arg_comment_string = print( - "Long lines with inline comments which are apart of (and not the only member of) an" - " argument list should have their comments appended to the reformatted string's" - " enclosing left parentheses.", # This comment gets thrown to the top. + ( # This comment gets thrown to the top. + "Long lines with inline comments which are apart of (and not the only member" + " of) an argument list should have their comments appended to the reformatted" + " string's enclosing left parentheses." + ), "Arg #2", "Arg #3", "Arg #4", @@ -645,23 +651,31 @@ def foo(): ) func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there.", + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), ) func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there.", # comment after comma + ( # comment after comma + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), ) func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there.", + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), ) func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there.", # comment after comma + ( # comment after comma + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), ) func_with_bad_parens_that_wont_fit_in_one_line( diff --git a/tests/data/preview/long_strings__regression.py b/tests/data/preview/long_strings__regression.py index 634db46a5e0..8b00e76f40e 100644 --- a/tests/data/preview/long_strings__regression.py +++ b/tests/data/preview/long_strings__regression.py @@ -679,9 +679,11 @@ class A: def foo(): some_func_call( "xxxxxxxxxx", - "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " - '"xxxx xxxxxxx xxxxxx xxxx; xxxx xxxxxx_xxxxx xxxxxx xxxx; ' - "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" ", + ( + "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " + '"xxxx xxxxxxx xxxxxx xxxx; xxxx xxxxxx_xxxxx xxxxxx xxxx; ' + "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" " + ), None, ("xxxxxxxxxxx",), ), @@ -690,9 +692,11 @@ def foo(): class A: def foo(): some_func_call( - "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " - "xxxx, ('xxxxxxx xxxxxx xxxx, xxxx') xxxxxx_xxxxx xxxxxx xxxx; " - "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" ", + ( + "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " + "xxxx, ('xxxxxxx xxxxxx xxxx, xxxx') xxxxxx_xxxxx xxxxxx xxxx; " + "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" " + ), None, ("xxxxxxxxxxx",), ), @@ -810,8 +814,10 @@ def foo(): ) lpar_and_rpar_have_comments = func_call( # LPAR Comment - "Long really ridiculous type of string that shouldn't really even exist at all. I" - " mean commmme onnn!!!", # Comma Comment + ( # Comma Comment + "Long really ridiculous type of string that shouldn't really even exist at all." + " I mean commmme onnn!!!" + ), ) # RPAR Comment cmd_fstring = ( diff --git a/tests/data/preview/remove_await_parens.py b/tests/data/preview/remove_await_parens.py index eb7dad340c3..571210a2d80 100644 --- a/tests/data/preview/remove_await_parens.py +++ b/tests/data/preview/remove_await_parens.py @@ -80,6 +80,7 @@ async def main(): # output import asyncio + # Control example async def main(): await asyncio.sleep(1) diff --git a/tests/data/py_310/pep_572_py310.py b/tests/data/py_310/pep_572_py310.py index 2aef589ce8d..cb82b2d23f8 100644 --- a/tests/data/py_310/pep_572_py310.py +++ b/tests/data/py_310/pep_572_py310.py @@ -2,3 +2,14 @@ x[a:=0] x[a:=0, b:=1] x[5, b:=0] + +# Walruses are allowed inside generator expressions on function calls since 3.10. +if any(match := pattern_error.match(s) for s in buffer): + if match.group(2) == data_not_available: + # Error OK to ignore. + pass + +f(a := b + c for c in range(10)) +f((a := b + c for c in range(10)), x) +f(y=(a := b + c for c in range(10))) +f(x, (a := b + c for c in range(10)), y=z, **q) diff --git a/tests/data/simple_cases/comments5.py b/tests/data/simple_cases/comments5.py index d83b6b8ff47..c8c38813d55 100644 --- a/tests/data/simple_cases/comments5.py +++ b/tests/data/simple_cases/comments5.py @@ -58,9 +58,9 @@ def decorated1(): ... -# Note: crappy but inevitable. The current design of EmptyLineTracker doesn't -# allow this to work correctly. The user will have to split those lines by -# hand. +# Note: this is fixed in +# Preview.empty_lines_before_class_or_def_with_leading_comments. +# In the current style, the user will have to split those lines by hand. some_instruction # This comment should be split from `some_instruction` by two lines but isn't. def g(): diff --git a/tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py b/tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py new file mode 100644 index 00000000000..6fea860adf6 --- /dev/null +++ b/tests/data/simple_cases/docstring_no_extra_empty_line_before_eof.py @@ -0,0 +1,4 @@ +# Make sure when the file ends with class's docstring, +# It doesn't add extra blank lines. +class ClassWithDocstring: + """A docstring.""" diff --git a/tests/data/trailing_comma_optional_parens1.py b/tests/data/trailing_comma_optional_parens1.py deleted file mode 100644 index 5ad29a8affd..00000000000 --- a/tests/data/trailing_comma_optional_parens1.py +++ /dev/null @@ -1,3 +0,0 @@ -if e1234123412341234.winerror not in (_winapi.ERROR_SEM_TIMEOUT, - _winapi.ERROR_PIPE_BUSY) or _check_timeout(t): - pass \ No newline at end of file diff --git a/tests/data/trailing_comma_optional_parens2.py b/tests/data/trailing_comma_optional_parens2.py deleted file mode 100644 index 2817073816e..00000000000 --- a/tests/data/trailing_comma_optional_parens2.py +++ /dev/null @@ -1,3 +0,0 @@ -if (e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') or - (8, 5, 8) <= get_tk_patchlevel() < (8, 6)): - pass \ No newline at end of file diff --git a/tests/data/trailing_comma_optional_parens3.py b/tests/data/trailing_comma_optional_parens3.py deleted file mode 100644 index e6a673ec537..00000000000 --- a/tests/data/trailing_comma_optional_parens3.py +++ /dev/null @@ -1,8 +0,0 @@ -if True: - if True: - if True: - return _( - "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas " - + "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.", - "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe", - ) % {"reported_username": reported_username, "report_reason": report_reason} \ No newline at end of file diff --git a/tests/test_black.py b/tests/test_black.py index 7f85fcdc409..9256698ea27 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -232,7 +232,7 @@ def _test_wip(self) -> None: sys.settrace(None) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, DEFAULT_MODE) + black.assert_stable(source, actual, black.FileMode()) def test_pep_572_version_detection(self) -> None: source, _ = read_data("py_38", "pep_572") @@ -341,6 +341,30 @@ def test_string_quotes(self) -> None: black.assert_equivalent(source, not_normalized) black.assert_stable(source, not_normalized, mode=mode) + def test_skip_source_first_line(self) -> None: + source, _ = read_data("miscellaneous", "invalid_header") + tmp_file = Path(black.dump_to_file(source)) + # Full source should fail (invalid syntax at header) + self.invokeBlack([str(tmp_file), "--diff", "--check"], exit_code=123) + # So, skipping the first line should work + result = BlackRunner().invoke( + black.main, [str(tmp_file), "-x", f"--config={EMPTY_CONFIG}"] + ) + self.assertEqual(result.exit_code, 0) + with open(tmp_file, encoding="utf8") as f: + actual = f.read() + self.assertFormatEqual(source, actual) + + def test_skip_source_first_line_when_mixing_newlines(self) -> None: + code_mixing_newlines = b"Header will be skipped\r\ni = [1,2,3]\nj = [1,2,3]\n" + expected = b"Header will be skipped\r\ni = [1, 2, 3]\nj = [1, 2, 3]\n" + with TemporaryDirectory() as workspace: + test_file = Path(workspace) / "skip_header.py" + test_file.write_bytes(code_mixing_newlines) + mode = replace(DEFAULT_MODE, skip_source_first_line=True) + ff(test_file, mode=mode, write_back=black.WriteBack.YES) + self.assertEqual(test_file.read_bytes(), expected) + def test_skip_magic_trailing_comma(self) -> None: source, _ = read_data("simple_cases", "expression") expected, _ = read_data( @@ -466,8 +490,10 @@ def err(msg: str, **kwargs: Any) -> None: 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.", + ( + "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) @@ -476,8 +502,10 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(out_lines[-1], "reformatted f3") self.assertEqual( unstyle(str(report)), - "2 files reformatted, 2 files left unchanged, 1 file failed to" - " reformat.", + ( + "2 files reformatted, 2 files left unchanged, 1 file failed to" + " reformat." + ), ) self.assertEqual(report.return_code, 123) report.failed(Path("e2"), "boom") @@ -486,8 +514,10 @@ def err(msg: str, **kwargs: Any) -> None: 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.", + ( + "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") @@ -496,8 +526,10 @@ def err(msg: str, **kwargs: Any) -> None: 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.", + ( + "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) @@ -506,22 +538,28 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(out_lines[-1], "f4 already well formatted, good job.") self.assertEqual( unstyle(str(report)), - "2 files reformatted, 3 files left unchanged, 2 files failed to" - " reformat.", + ( + "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.", + ( + "2 files would be reformatted, 3 files would be left unchanged, 2" + " files would fail to reformat." + ), ) report.check = False report.diff = True self.assertEqual( unstyle(str(report)), - "2 files would be reformatted, 3 files would be left unchanged, 2" - " files would fail to reformat.", + ( + "2 files would be reformatted, 3 files would be left unchanged, 2" + " files would fail to reformat." + ), ) def test_report_quiet(self) -> None: @@ -563,8 +601,10 @@ def err(msg: str, **kwargs: Any) -> None: 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.", + ( + "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) @@ -572,8 +612,10 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(len(err_lines), 1) self.assertEqual( unstyle(str(report)), - "2 files reformatted, 2 files left unchanged, 1 file failed to" - " reformat.", + ( + "2 files reformatted, 2 files left unchanged, 1 file failed to" + " reformat." + ), ) self.assertEqual(report.return_code, 123) report.failed(Path("e2"), "boom") @@ -582,8 +624,10 @@ def err(msg: str, **kwargs: Any) -> None: 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.", + ( + "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") @@ -591,8 +635,10 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(len(err_lines), 2) self.assertEqual( unstyle(str(report)), - "2 files reformatted, 2 files left unchanged, 2 files failed to" - " reformat.", + ( + "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) @@ -600,22 +646,28 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(len(err_lines), 2) self.assertEqual( unstyle(str(report)), - "2 files reformatted, 3 files left unchanged, 2 files failed to" - " reformat.", + ( + "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.", + ( + "2 files would be reformatted, 3 files would be left unchanged, 2" + " files would fail to reformat." + ), ) report.check = False report.diff = True self.assertEqual( unstyle(str(report)), - "2 files would be reformatted, 3 files would be left unchanged, 2" - " files would fail to reformat.", + ( + "2 files would be reformatted, 3 files would be left unchanged, 2" + " files would fail to reformat." + ), ) def test_report_normal(self) -> None: @@ -659,8 +711,10 @@ def err(msg: str, **kwargs: Any) -> None: 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.", + ( + "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) @@ -669,8 +723,10 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(out_lines[-1], "reformatted f3") self.assertEqual( unstyle(str(report)), - "2 files reformatted, 2 files left unchanged, 1 file failed to" - " reformat.", + ( + "2 files reformatted, 2 files left unchanged, 1 file failed to" + " reformat." + ), ) self.assertEqual(report.return_code, 123) report.failed(Path("e2"), "boom") @@ -679,8 +735,10 @@ def err(msg: str, **kwargs: Any) -> None: 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.", + ( + "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") @@ -688,8 +746,10 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(len(err_lines), 2) self.assertEqual( unstyle(str(report)), - "2 files reformatted, 2 files left unchanged, 2 files failed to" - " reformat.", + ( + "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) @@ -697,22 +757,28 @@ def err(msg: str, **kwargs: Any) -> None: self.assertEqual(len(err_lines), 2) self.assertEqual( unstyle(str(report)), - "2 files reformatted, 3 files left unchanged, 2 files failed to" - " reformat.", + ( + "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.", + ( + "2 files would be reformatted, 3 files would be left unchanged, 2" + " files would fail to reformat." + ), ) report.check = False report.diff = True self.assertEqual( unstyle(str(report)), - "2 files would be reformatted, 3 files would be left unchanged, 2" - " files would fail to reformat.", + ( + "2 files would be reformatted, 3 files would be left unchanged, 2" + " files would fail to reformat." + ), ) def test_lib2to3_parse(self) -> None: diff --git a/tests/test_blackd.py b/tests/test_blackd.py index db9a1652f8c..5b6461f7685 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -177,6 +177,20 @@ async def test_blackd_invalid_line_length(self) -> None: ) self.assertEqual(response.status, 400) + @unittest_run_loop + async def test_blackd_skip_first_source_line(self) -> None: + invalid_first_line = b"Header will be skipped\r\ni = [1,2,3]\nj = [1,2,3]\n" + expected_result = b"Header will be skipped\r\ni = [1, 2, 3]\nj = [1, 2, 3]\n" + response = await self.client.post("/", data=invalid_first_line) + self.assertEqual(response.status, 400) + response = await self.client.post( + "/", + data=invalid_first_line, + headers={blackd.SKIP_SOURCE_FIRST_LINE: "true"}, + ) + self.assertEqual(response.status, 200) + self.assertEqual(await response.read(), expected_result) + @unittest_run_loop async def test_blackd_preview(self) -> None: response = await self.client.post(