Skip to content

Commit

Permalink
Resolve #1353: Added support for output format plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
timothycrosley committed Jul 27, 2020
1 parent ca19112 commit b40e584
Show file tree
Hide file tree
Showing 15 changed files with 509 additions and 184 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ NOTE: isort follows the [semver](https://semver.org/) versioning standard.
- Implemented #1214: Added support for git_hook lazy option (Thanks @sztamas!)
- Implemented #941: Added an additional `multi_line_output` mode for more compact formatting (Thanks @sztamas!)
- Implemented #1020: Option for LOCALFOLDER.
- Implemented #1353: Added support for output formatting plugins.
- `# isort: split` can now be used at the end of an import line.
- Fixed #1339: Extra indent is not preserved when isort:skip is used in nested imports.
- Fixed #1348: `--diff` works incorrectly with files that have CRLF line endings.
Expand Down
22 changes: 22 additions & 0 deletions example_isort_formatting_plugin/example_isort_formatting_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import black

import isort


def black_format_import_section(
contents: str, extension: str, config: isort.settings.Config
) -> str:
"""Formats the given import section using black."""
if extension.lower() not in ("pyi", "py"):
return contents

try:
return black.format_file_contents(
contents,
fast=True,
mode=black.FileMode(
is_pyi=extension.lower() == "pyi", line_length=config.line_length,
),
)
except black.NothingChanged:
return contents
173 changes: 173 additions & 0 deletions example_isort_formatting_plugin/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions example_isort_formatting_plugin/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[tool.poetry]
name = "example_isort_formatting_plugin"
version = "0.0.1"
description = "An example plugin that modifies isort formatting using black."
authors = ["Timothy Crosley <[email protected]>"]
license = "MIT"

[tool.poetry.plugins."isort.formatters"]
example = "example_isort_formatting_plugin:black_format_import_section"

[tool.poetry.dependencies]
python = "^3.6"
isort = "^5.1.4"
black = "^19.10b0"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
8 changes: 8 additions & 0 deletions isort/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,11 @@ def __init__(self, profile: str):
f"Available profiles: {','.join(profiles)}."
)
self.profile = profile


class FormattingPluginDoesNotExist(ISortError):
"""Raised when a formatting plugin is set by the user that doesn't exist"""

def __init__(self, formatter: str):
super().__init__(f"Specified formatting plugin of {formatter} does not exist. ")
self.formatter = formatter
9 changes: 9 additions & 0 deletions isort/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,13 @@ def _build_arg_parser() -> argparse.ArgumentParser:
"Still it can be a great shortcut for collecting imports every once in a while when you put"
" them in the middle of a file.",
)
parser.add_argument(
"--formatter",
dest="formatter",
type=str,
help="Specifies the name of a formatting plugin to use when producing output.",
)

# deprecated options
parser.add_argument(
"--recursive",
Expand Down Expand Up @@ -686,6 +693,8 @@ def _preconvert(item):
return item.name
elif isinstance(item, Path):
return str(item)
elif callable(item) and hasattr(item, "__name__"):
return item.__name__
else:
raise TypeError("Unserializable object {} of type {}".format(item, type(item)))

Expand Down
5 changes: 5 additions & 0 deletions isort/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ def sorted_imports(
while output and output[0].strip() == "":
output.pop(0)

if config.formatting_function:
output = config.formatting_function(
parsed.line_separator.join(output), extension, config
).splitlines()

output_at = 0
if parsed.import_index < parsed.original_line_count:
output_at = parsed.import_index
Expand Down
14 changes: 13 additions & 1 deletion isort/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from . import stdlibs
from ._future import dataclass, field
from ._vendored import toml
from .exceptions import InvalidSettingsPath, ProfileDoesNotExist
from .exceptions import FormattingPluginDoesNotExist, InvalidSettingsPath, ProfileDoesNotExist
from .profiles import profiles
from .sections import DEFAULT as SECTION_DEFAULTS
from .sections import FIRSTPARTY, FUTURE, LOCALFOLDER, STDLIB, THIRDPARTY
Expand Down Expand Up @@ -173,6 +173,8 @@ class _Config:
remove_redundant_aliases: bool = False
float_to_top: bool = False
filter_files: bool = False
formatter: str = ""
formatting_function: Optional[Callable[[str, str, object], str]] = None

def __post_init__(self):
py_version = self.py_version
Expand Down Expand Up @@ -348,6 +350,16 @@ def __init__(
path_root / path for path in combined_config.get("src_paths", ())
)

if "formatter" in combined_config:
import pkg_resources

for plugin in pkg_resources.iter_entry_points("isort.formatters"):
if plugin.name == combined_config["formatter"]:
combined_config["formatting_function"] = plugin.load()
break
else:
raise FormattingPluginDoesNotExist(combined_config["formatter"])

# Remove any config values that are used for creating config object but
# aren't defined in dataclass
combined_config.pop("source", None)
Expand Down
Loading

0 comments on commit b40e584

Please sign in to comment.