pax_global_header00006660000000000000000000000064137001225560014513gustar00rootroot0000000000000052 comment=f1f93ede9dd8b69d08d0a73fe59f77978146aca7 pyls-black-0.4.6/000077500000000000000000000000001370012255600135635ustar00rootroot00000000000000pyls-black-0.4.6/.circleci/000077500000000000000000000000001370012255600154165ustar00rootroot00000000000000pyls-black-0.4.6/.circleci/config.yml000066400000000000000000000013031370012255600174030ustar00rootroot00000000000000version: 2 jobs: lint: working_directory: ~/project docker: - image: circleci/python:3.7.2-stretch steps: - checkout - run: | python3 -m venv ~/venv . ~/venv/bin/activate pip install -e .[dev] - run: | . ~/venv/bin/activate make lint test: working_directory: ~/project docker: - image: circleci/python:3.7.2-stretch steps: - checkout - run: | python3 -m venv ~/venv . ~/venv/bin/activate pip install -e .[dev] - run: | . ~/venv/bin/activate make test workflows: version: 2 build_and_test: jobs: - lint - test pyls-black-0.4.6/.gitignore000066400000000000000000000001031370012255600155450ustar00rootroot00000000000000.mypy_cache *.egg-info *.pyc .pytest_cache .ropeproject build dist pyls-black-0.4.6/LICENSE000066400000000000000000000020571370012255600145740ustar00rootroot00000000000000MIT License Copyright (c) 2018 Rupert Bedford Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyls-black-0.4.6/Makefile000066400000000000000000000006261370012255600152270ustar00rootroot00000000000000lint: flake8 black-check isort-check mypy flake8: flake8 . black: black . black-check: black --diff --check . isort: isort . isort-check: isort --check-only . mypy: mypy . test: pytest -v . build: lint test python3 setup.py sdist bdist_wheel test-upload: twine upload --repository-url https://test.pypi.org/legacy/ dist/* upload: twine upload dist/* clean: rm -rf dist .PHONY: build pyls-black-0.4.6/README.md000066400000000000000000000024201370012255600150400ustar00rootroot00000000000000# pyls-black [![PyPI](https://img.shields.io/pypi/v/pyls-black.svg)](https://pypi.org/project/pyls-black/) [![CircleCI branch](https://img.shields.io/circleci/project/github/rupert/pyls-black/master.svg)](https://circleci.com/gh/rupert/pyls-black) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyls-black.svg) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) > [Black](https://github.com/ambv/black) plugin for the [Python Language Server](https://github.com/palantir/python-language-server). In the same `virtualenv` as `python-language-server`: ```shell pip3 install pyls-black ``` To avoid unexpected results you should make sure `yapf` and `autopep8` are not installed. * `pyls-black` can either format an entire file or just the selected text. * The code will only be formatted if it is syntactically valid Python. * Text selections are treated as if they were a separate Python file. Unfortunately this means you can't format an indented block of code. * `pyls-black` will use your project's [pyproject.toml](https://github.com/ambv/black#pyprojecttoml) if it has one. See the [wiki](https://github.com/rupert/pyls-black/wiki) for instructions on how to use `pyls-black` with popular text editors. pyls-black-0.4.6/pyls_black/000077500000000000000000000000001370012255600157065ustar00rootroot00000000000000pyls-black-0.4.6/pyls_black/__init__.py000066400000000000000000000000001370012255600200050ustar00rootroot00000000000000pyls-black-0.4.6/pyls_black/plugin.py000066400000000000000000000054541370012255600175660ustar00rootroot00000000000000from typing import Dict import black import toml from pyls import hookimpl @hookimpl(tryfirst=True) def pyls_format_document(document): return format_document(document) @hookimpl(tryfirst=True) def pyls_format_range(document, range): range["start"]["character"] = 0 range["end"]["line"] += 1 range["end"]["character"] = 0 return format_document(document, range) def format_document(document, range=None): if range: start = range["start"]["line"] end = range["end"]["line"] text = "".join(document.lines[start:end]) else: text = document.source range = { "start": {"line": 0, "character": 0}, "end": {"line": len(document.lines), "character": 0}, } config = load_config(document.path) try: formatted_text = format_text(text=text, config=config) except ( ValueError, # raised when the file is already formatted correctly black.NothingChanged, # raised when the file being formatted has an indentation error IndentationError, # raised when black produces invalid Python code or formats the file # differently on the second pass AssertionError, ): return [] return [{"range": range, "newText": formatted_text}] def format_text(*, text, config): mode = black.FileMode( target_versions=config["target_version"], line_length=config["line_length"], is_pyi=config["pyi"], string_normalization=not config["skip_string_normalization"], ) return black.format_file_contents(text, fast=config["fast"], mode=mode) def load_config(filename: str) -> Dict: defaults = { "line_length": 88, "fast": False, "pyi": filename.endswith(".pyi"), "skip_string_normalization": False, "target_version": set(), } root = black.find_project_root((filename,)) pyproject_filename = root / "pyproject.toml" if not pyproject_filename.is_file(): return defaults try: pyproject_toml = toml.load(str(pyproject_filename)) except (toml.TomlDecodeError, OSError): return defaults file_config = pyproject_toml.get("tool", {}).get("black", {}) file_config = { key.replace("--", "").replace("-", "_"): value for key, value in file_config.items() } config = { key: file_config.get(key, default_value) for key, default_value in defaults.items() } if file_config.get("target_version"): target_version = set( black.TargetVersion[x.upper()] for x in file_config["target_version"] ) elif file_config.get("py36"): target_version = black.PY36_VERSIONS else: target_version = set() config["target_version"] = target_version return config pyls-black-0.4.6/pyproject.toml000066400000000000000000000002171370012255600164770ustar00rootroot00000000000000[tool.black] target-version = ['py36', 'py37', 'py38'] exclude = ''' /( \.venv | \.git | \.mypy_cache | build | dist | fixtures )/ ''' pyls-black-0.4.6/setup.cfg000066400000000000000000000014071370012255600154060ustar00rootroot00000000000000[metadata] name = pyls-black version = 0.4.6 author = Rupert Bedford author_email = rupert@rupertb.com description = Black plugin for the Python Language Server url = https://github.com/rupert/pyls-black long_description = file: README.md long_description_content_type = text/markdown classifiers = Programming Language :: Python License :: OSI Approved :: MIT License Operating System :: OS Independent [options] packages = find: install_requires = python-language-server; black>=19.3b0; toml python_requires = >= 3.6 [options.entry_points] pyls = pyls_black = pyls_black.plugin [options.extras_require] dev = isort>=5.0; flake8; pytest; mypy; pytest [flake8] max-line-length = 88 ignore = E203 [mypy] ignore_missing_imports = true [isort] profile = black pyls-black-0.4.6/setup.py000066400000000000000000000001051370012255600152710ustar00rootroot00000000000000from setuptools import setup if __name__ == "__main__": setup() pyls-black-0.4.6/tests/000077500000000000000000000000001370012255600147255ustar00rootroot00000000000000pyls-black-0.4.6/tests/fixtures/000077500000000000000000000000001370012255600165765ustar00rootroot00000000000000pyls-black-0.4.6/tests/fixtures/config/000077500000000000000000000000001370012255600200435ustar00rootroot00000000000000pyls-black-0.4.6/tests/fixtures/config/config.txt000066400000000000000000000000531370012255600220470ustar00rootroot00000000000000run(these, arguments, should, be, wrapped) pyls-black-0.4.6/tests/fixtures/config/pyproject.toml000066400000000000000000000001301370012255600227510ustar00rootroot00000000000000[tool.black] line-length = 20 --fast = true pyi = true skip-string-normalization = true pyls-black-0.4.6/tests/fixtures/formatted.pyi000066400000000000000000000000551370012255600213060ustar00rootroot00000000000000def foo() -> None: ... def bar() -> int: ... pyls-black-0.4.6/tests/fixtures/formatted.txt000066400000000000000000000000231370012255600213170ustar00rootroot00000000000000a = "hello" b = 42 pyls-black-0.4.6/tests/fixtures/invalid.txt000066400000000000000000000000141370012255600207600ustar00rootroot00000000000000 x = 1+2 pyls-black-0.4.6/tests/fixtures/py36/000077500000000000000000000000001370012255600173775ustar00rootroot00000000000000pyls-black-0.4.6/tests/fixtures/py36/pyproject.toml000066400000000000000000000000311370012255600223050ustar00rootroot00000000000000[tool.black] py36 = true pyls-black-0.4.6/tests/fixtures/pyproject.toml000066400000000000000000000000001370012255600215000ustar00rootroot00000000000000pyls-black-0.4.6/tests/fixtures/target_version/000077500000000000000000000000001370012255600216315ustar00rootroot00000000000000pyls-black-0.4.6/tests/fixtures/target_version/pyproject.toml000066400000000000000000000000471370012255600245460ustar00rootroot00000000000000[tool.black] target-version = ['py27'] pyls-black-0.4.6/tests/fixtures/unformatted.pyi000066400000000000000000000000661370012255600216530ustar00rootroot00000000000000def foo() -> None: ... def bar() -> int: ... pyls-black-0.4.6/tests/fixtures/unformatted.txt000066400000000000000000000000241370012255600216630ustar00rootroot00000000000000a = 'hello' b = 42 pyls-black-0.4.6/tests/test_plugin.py000066400000000000000000000125731370012255600176440ustar00rootroot00000000000000import types from pathlib import Path from unittest.mock import Mock import black import pkg_resources import pytest from pyls import uris from pyls.workspace import Document, Workspace from pyls_black.plugin import load_config, pyls_format_document, pyls_format_range here = Path(__file__).parent fixtures_dir = here / "fixtures" @pytest.fixture def workspace(tmpdir): """Return a workspace.""" return Workspace(uris.from_fs_path(str(tmpdir)), Mock()) @pytest.fixture def unformatted_document(workspace): path = fixtures_dir / "unformatted.txt" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def unformatted_pyi_document(workspace): path = fixtures_dir / "unformatted.pyi" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def formatted_document(workspace): path = fixtures_dir / "formatted.txt" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def formatted_pyi_document(workspace): path = fixtures_dir / "formatted.pyi" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def invalid_document(workspace): path = fixtures_dir / "invalid.txt" uri = f"file:/{path}" return Document(uri, workspace) @pytest.fixture def config_document(workspace): path = fixtures_dir / "config" / "config.txt" uri = f"file:/{path}" return Document(uri, workspace) def test_pyls_format_document(unformatted_document, formatted_document): result = pyls_format_document(unformatted_document) assert result == [ { "range": { "start": {"line": 0, "character": 0}, "end": {"line": 2, "character": 0}, }, "newText": formatted_document.source, } ] def test_pyls_format_pyi_document(unformatted_pyi_document, formatted_pyi_document): result = pyls_format_document(unformatted_pyi_document) assert result == [ { "range": { "start": {"line": 0, "character": 0}, "end": {"line": 5, "character": 0}, }, "newText": formatted_pyi_document.source, } ] def test_pyls_format_document_unchanged(formatted_document): result = pyls_format_document(formatted_document) assert result == [] def test_pyls_format_pyi_document_unchanged(formatted_pyi_document): result = pyls_format_document(formatted_pyi_document) assert result == [] def test_pyls_format_document_syntax_error(invalid_document): result = pyls_format_document(invalid_document) assert result == [] def test_pyls_format_document_with_config(config_document): result = pyls_format_document(config_document) assert result == [ { "range": { "start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 0}, }, "newText": ( "run(\n" " these,\n" " arguments,\n" " should,\n" " be,\n" " wrapped,\n" ")\n" ), } ] @pytest.mark.parametrize( ("start", "end", "expected"), [(0, 0, 'a = "hello"\n'), (1, 1, "b = 42\n"), (0, 1, 'a = "hello"\nb = 42\n')], ) def test_pyls_format_range(unformatted_document, start, end, expected): range = { "start": {"line": start, "character": 0}, "end": {"line": end, "character": 0}, } result = pyls_format_range(unformatted_document, range=range) assert result == [ { "range": { "start": {"line": start, "character": 0}, "end": {"line": end + 1, "character": 0}, }, "newText": expected, } ] def test_pyls_format_range_unchanged(formatted_document): range = {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 0}} result = pyls_format_range(formatted_document, range=range) assert result == [] def test_pyls_format_range_syntax_error(invalid_document): range = {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 0}} result = pyls_format_range(invalid_document, range=range) assert result == [] def test_load_config(): config = load_config(str(fixtures_dir / "config" / "example.py")) # TODO split into smaller tests assert config == { "line_length": 20, "target_version": set(), "pyi": True, "fast": True, "skip_string_normalization": True, } def test_load_config_target_version(): config = load_config(str(fixtures_dir / "target_version" / "example.py")) assert config["target_version"] == {black.TargetVersion.PY27} def test_load_config_py36(): config = load_config(str(fixtures_dir / "py36" / "example.py")) assert config["target_version"] == black.PY36_VERSIONS def test_load_config_defaults(): config = load_config(str(fixtures_dir / "example.py")) assert config == { "line_length": 88, "target_version": set(), "pyi": False, "fast": False, "skip_string_normalization": False, } def test_entry_point(): distribution = pkg_resources.get_distribution("pyls-black") entry_point = distribution.get_entry_info("pyls", "pyls_black") assert entry_point is not None module = entry_point.load() assert isinstance(module, types.ModuleType)