Skip to content

Commit

Permalink
Bump consolekit version, add new CLI options and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
domdfcoding committed Jan 27, 2021
1 parent 3084d8d commit d8a06f0
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 75 deletions.
9 changes: 0 additions & 9 deletions demo.py

This file was deleted.

17 changes: 11 additions & 6 deletions formate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
# this package
from formate.classes import FormateConfigDict, Hook
from formate.config import parse_hooks, wants_global_config
from formate.utils import syntaxerror_for_file

__all__ = ["call_hooks", "isort_hook", "yapf_hook", "Reformatter", "reformat_file"]

Expand Down Expand Up @@ -227,9 +228,13 @@ def to_file(self) -> None:
self.file_to_format.write_text(self.to_string())


def reformat_file(filename: PathLike, config: FormateConfigDict, colour: ColourTrilean = None):
def reformat_file(
filename: PathLike,
config: FormateConfigDict,
colour: ColourTrilean = None,
):
"""
Reformat the given file.
Reformat the given file, and show the diff if changes were made.
:param filename: The filename to reformat.
:param config: The ``formate`` configuration, parsed from a TOML file (or similar).
Expand All @@ -238,11 +243,11 @@ def reformat_file(filename: PathLike, config: FormateConfigDict, colour: ColourT

r = Reformatter(filename, config)

if r.run():
with syntaxerror_for_file(filename):
ret = r.run()

if ret:
click.echo(r.get_diff(), color=resolve_color_default(colour))
ret = 1
else:
ret = 0

r.to_file()

Expand Down
40 changes: 27 additions & 13 deletions formate/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,23 @@

# stdlib
import sys
from typing import List, Optional

# 3rd party
import click
from consolekit import click_command
from consolekit.options import MultiValueOption, colour_option, verbose_option
from consolekit.options import MultiValueOption, colour_option, flag_option, verbose_option
from consolekit.terminal_colours import ColourTrilean, resolve_color_default
from consolekit.tracebacks import handle_tracebacks, traceback_option

if False:
# stdlib
from typing import List, Optional

# 3rd party
from consolekit.terminal_colours import ColourTrilean
# this package
from formate import Reformatter

__all__ = ["main"]


@flag_option("--diff", "show_diff", help="Show a diff of changes made")
@traceback_option()
@colour_option()
@verbose_option()
@click.option(
Expand All @@ -70,6 +71,8 @@ def main(
exclude: "Optional[List[str]]",
colour: "ColourTrilean" = None,
verbose: bool = False,
show_traceback: bool = False,
show_diff: bool = False,
):
"""
Reformat the given Python source files.
Expand All @@ -80,8 +83,8 @@ def main(
import re

# this package
from formate import reformat_file
from formate.config import load_toml
from formate.utils import SyntaxTracebackHandler, syntaxerror_for_file

retv = 0

Expand All @@ -95,11 +98,22 @@ def main(
if re.match(fnmatch.translate(pattern), str(path)):
continue

ret_for_file = reformat_file(path, config=config, colour=colour)
if ret_for_file == 1 and verbose:
click.echo(f"Reformatting {path}.")
elif verbose > 1:
click.echo(f"Checking {path}.")
r = Reformatter(path, config=config)

with handle_tracebacks(show_traceback, cls=SyntaxTracebackHandler):
with syntaxerror_for_file(path):
ret_for_file = r.run()

if ret_for_file:
if verbose:
click.echo(f"Reformatting {path}.")
if show_diff:
click.echo(r.get_diff(), color=resolve_color_default(colour))
else:
if verbose:
click.echo(f"Checking {path}.")

r.to_file()

retv |= ret_for_file

Expand Down
83 changes: 40 additions & 43 deletions formate/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,27 @@

# stdlib
import ast
import json
import os
import re
import sys
from contextlib import contextmanager
from itertools import starmap
from operator import itemgetter
from typing import Any, Callable, Dict, List, Optional, Tuple
from typing import Dict, List, Tuple

# 3rd party
import asttokens # type: ignore
from domdf_python_tools.compat import importlib_metadata
import click
from consolekit import terminal_colours
from consolekit.tracebacks import TracebackHandler
from domdf_python_tools.import_tools import discover_entry_points_by_name
from domdf_python_tools.typing import PathLike

# this package
from formate.classes import EntryPoint, Hook
from formate.exceptions import HookNotFoundError

__all__ = ["Rewriter", "import_entry_points", "normalize"]
__all__ = ["Rewriter", "import_entry_points", "normalize", "SyntaxTracebackHandler", "syntaxerror_for_file"]

_normalize_pattern = re.compile(r"[-_.]+")

Expand All @@ -59,45 +65,6 @@ def normalize(name: str) -> str:
return _normalize_pattern.sub('-', name).lower()


def discover_entry_points_by_name(
group_name: str,
name_match_func: Optional[Callable[[Any], bool]] = None,
object_match_func: Optional[Callable[[Any], bool]] = None,
) -> Dict[str, Any]:
"""
Returns a mapping of entry point names to the entry points in the given category,
optionally filtered by ``match_func``.
.. versionadded:: 2.5.0
:param group_name: The entry point group name, e.g. ``'entry_points'``.
:param name_match_func: Function taking the entry point name and returning :py:obj:`True`
if the entry point is to be included in the output.
:default name_match_func: :py:obj:`None`, which includes all entry points.
:param object_match_func: Function taking an object and returning :py:obj:`True`
if the object is to be included in the output.
:default object_match_func: :py:obj:`None`, which includes all objects.
:return: List of matching objects.
""" # noqa: D400

matching_objects = {}

for entry_point in importlib_metadata.entry_points().get(group_name, ()):

if name_match_func is not None and not name_match_func(entry_point.name):
continue

entry_point_obj = entry_point.load()

if object_match_func is not None and not object_match_func(entry_point_obj):
continue

matching_objects[entry_point.name] = entry_point_obj

return matching_objects


def import_entry_points(hooks: List[Hook]) -> Dict[str, EntryPoint]:
"""
Given a list of hooks, import the corresponding entry point and
Expand Down Expand Up @@ -181,3 +148,33 @@ def record_replacement(self, text_range: Tuple[int, int], new_source: str):
"""

self.replacements.append((text_range, new_source))


class SyntaxTracebackHandler(TracebackHandler):
"""
Subclass of :class:`consolekit.tracebacks.TracebackHandler` to additionally handle :exc:`SyntaxError`.
"""

def handle_SyntaxError(self, e: SyntaxError): # noqa: D102
click.echo(terminal_colours.Fore.RED(f"Fatal: {e.__class__.__name__}: {e}"), err=True)
sys.exit(126)


@contextmanager
def syntaxerror_for_file(filename: PathLike):
"""
Context manager to catch :exc:`SyntaxError` and set its filename to ``filename``
if the current filename is ``<unknown>``.
This is useful for syntax errors raised when parsing source into an AST.
:param filename:
""" # noqa: D400

try:
yield
except SyntaxError as e:
if e.filename == "<unknown>":
e.filename = os.fspath(filename)

raise e
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ asttokens>=2.0.4
attr-utils>=0.5.5
attrs>=20.3.0
click>=7.1.2
consolekit>=0.9.0
consolekit>=1.0.0
domdf-python-tools>=2.5.0
isort<=5.6.4,>=5.5.2
toml>=0.10.2
Expand Down

0 comments on commit d8a06f0

Please sign in to comment.