Skip to content

Commit

Permalink
make format of fixit's terminal output configurable (#437)
Browse files Browse the repository at this point in the history
* format of fixit's terminal output can be configured

* bind output-format and output-template to cwd; provide presets

* fix linting and typechecking errors

* refactor output format and config handling for cwd, more tests, reword docs

* Note plans to change default output format in future release

---------

Co-authored-by: Amethyst Reese <[email protected]>
  • Loading branch information
jvllmr and amyreese authored Jul 16, 2024
1 parent 75b8b87 commit a63d6b2
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 21 deletions.
12 changes: 11 additions & 1 deletion docs/guide/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ The following options are available for all commands:
two tags.


.. attribute:: --output-format / -o FORMAT_TYPE

Override how Fixit prints violations to the terminal.

See :attr:`output-format` for available formats.

.. attribute:: --output-template TEMPLATE

Override the python formatting template to use with ``output-format = 'custom'``.

``lint``
^^^^^^^^

Expand Down Expand Up @@ -72,7 +82,7 @@ the input read from STDIN, and the fixed output printed to STDOUT (ignoring
$ fixit fix [--interactive | --automatic [--diff]] [PATH ...]
.. attribute:: --interactive / -i

Interactively prompt the user to apply or decline suggested fixes for
each auto-fix available. *default*

Expand Down
49 changes: 47 additions & 2 deletions docs/guide/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ The main configuration table.

.. code-block:: toml
root = True
root = true
enable-root-import = "src"
enable = ["orange.rules"]
Expand Down Expand Up @@ -129,7 +129,7 @@ The main configuration table.
.. code-block:: toml
python-version = "3.10"
Defaults to the currently active version of Python.
Set to empty string ``""`` to disable target version checking.

Expand All @@ -151,6 +151,51 @@ The main configuration table.
Alternative formatting styles can be added by implementing the
:class:`~fixit.Formatter` interface.

.. attribute:: output-format
:type: str

Choose one of the presets for terminal output formatting.
This option is inferred based on the current working directory or from
an explicity specified config file -- subpath overrides will be ignored.

Can be one of:

- ``custom``: Specify your own format using the :attr:`output-template`
option below.
- ``fixit``: Fixit's default output format.
- ``vscode``: A format that provides clickable paths for Visual Studio Code.

.. note::

The default output format is planned to change to ``vscode`` in
the next feature release, expected as part of ``v2.3`` or ``v3.0``.
If you are sensitive to output formats changing, specify your preferred
format in your project configs accordingly.

.. attribute:: output-template
:type: str

Sets the format of output printed to terminal.
Python formatting is used in the background to fill in data.
Only active with :attr:`output-format` set to ``custom``.

This option is inferred based on the current working directory or from
an explicity specified config file -- subpath overrides will be ignored.

Supported variables:

- ``message``: Message emitted by the applied rule.

- ``path``: Path to affected file.

- ``result``: Raw :class:`~fixit.Result` object.

- ``rule_name``: Name of the applied rule.

- ``start_col``: Start column of affected code.

- ``start_line``: Start line of affected code.


.. _rule-options:

Expand Down
6 changes: 6 additions & 0 deletions docs/guide/integrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ your repository.
To read more about how you can customize your pre-commit configuration,
see the `pre-commit docs <https://pre-commit.com/#pre-commit-configyaml---hooks>`__.


VSCode
^^^^^^
For better integration with Visual Studio Code setting ``output-format`` can be set to ``vscode``.
That way VSCode opens the editor at the right position when clicking on code locations in Fixit's terminal output.
40 changes: 33 additions & 7 deletions src/fixit/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,26 @@
from .config import collect_rules, generate_config
from .engine import LintRunner
from .format import format_module
from .ftypes import Config, FileContent, LintViolation, Options, Result, STDIN
from .ftypes import (
Config,
FileContent,
LintViolation,
Options,
OutputFormat,
Result,
STDIN,
)

LOG = logging.getLogger(__name__)


def print_result(
result: Result, *, show_diff: bool = False, stderr: bool = False
result: Result,
*,
show_diff: bool = False,
stderr: bool = False,
output_format: OutputFormat = OutputFormat.fixit,
output_template: str = "",
) -> int:
"""
Print linting results in a simple format designed for human eyes.
Expand All @@ -46,11 +59,24 @@ def print_result(
message = result.violation.message
if result.violation.autofixable:
message += " (has autofix)"
click.secho(
f"{path}@{start_line}:{start_col} {rule_name}: {message}",
fg="yellow",
err=stderr,
)

if output_format == OutputFormat.fixit:
line = f"{path}@{start_line}:{start_col} {rule_name}: {message}"
elif output_format == OutputFormat.vscode:
line = f"{path}:{start_line}:{start_col} {rule_name}: {message}"
elif output_format == OutputFormat.custom:
line = output_template.format(
message=message,
path=path,
result=result,
rule_name=rule_name,
start_col=start_col,
start_line=start_line,
)
else:
raise NotImplementedError(f"output-format = {output_format!r}")
click.secho(line, fg="yellow", err=stderr)

if show_diff and result.violation.diff:
echo_color_precomputed_diff(result.violation.diff)
return True
Expand Down
38 changes: 34 additions & 4 deletions src/fixit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from .api import fixit_paths, print_result
from .config import collect_rules, generate_config, parse_rule
from .ftypes import Config, LSPOptions, Options, QualifiedRule, Tags
from .ftypes import Config, LSPOptions, Options, OutputFormat, QualifiedRule, Tags
from .rule import LintRule
from .testing import generate_lint_rule_test_cases
from .util import capture
Expand Down Expand Up @@ -72,12 +72,28 @@ def f(v: int) -> str:
default="",
help="Override configured rules",
)
@click.option(
"--output-format",
"-o",
type=click.Choice([o.name for o in OutputFormat], case_sensitive=False),
show_choices=True,
default=None,
help="Select output format type",
)
@click.option(
"--output-template",
type=str,
default="",
help="Python format template to use with output format 'custom'",
)
def main(
ctx: click.Context,
debug: Optional[bool],
config_file: Optional[Path],
tags: str,
rules: str,
output_format: Optional[OutputFormat],
output_template: str,
) -> None:
level = logging.WARNING
if debug is not None:
Expand All @@ -95,6 +111,8 @@ def main(
if r
}
),
output_format=output_format,
output_template=output_template,
)


Expand All @@ -121,10 +139,15 @@ def lint(
visited: Set[Path] = set()
dirty: Set[Path] = set()
autofixes = 0
config = generate_config(options=options)
for result in fixit_paths(paths, options=options):
visited.add(result.path)

if print_result(result, show_diff=diff):
if print_result(
result,
show_diff=diff,
output_format=config.output_format,
output_template=config.output_template,
):
dirty.add(result.path)
if result.violation:
exit_code |= 1
Expand Down Expand Up @@ -179,11 +202,18 @@ def fix(
generator = capture(
fixit_paths(paths, autofix=autofix, options=options, parallel=False)
)
config = generate_config(options=options)
for result in generator:
visited.add(result.path)
# for STDIN, we need STDOUT to equal the fixed content, so
# move everything else to STDERR
if print_result(result, show_diff=interactive or diff, stderr=is_stdin):
if print_result(
result,
show_diff=interactive or diff,
stderr=is_stdin,
output_format=config.output_format,
output_template=config.output_template,
):
dirty.add(result.path)
if autofix and result.violation and result.violation.autofixable:
autofixes += 1
Expand Down
31 changes: 29 additions & 2 deletions src/fixit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import platform
import sys
from contextlib import contextmanager, ExitStack

from pathlib import Path
from types import ModuleType
from typing import (
Expand Down Expand Up @@ -37,6 +38,7 @@
is_collection,
is_sequence,
Options,
OutputFormat,
QualifiedRule,
QualifiedRuleRegex,
RawConfig,
Expand All @@ -55,6 +57,7 @@
FIXIT_CONFIG_FILENAMES = ("fixit.toml", ".fixit.toml", "pyproject.toml")
FIXIT_LOCAL_MODULE = "fixit.local"


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -402,6 +405,8 @@ def merge_configs(
rule_options: RuleOptionsTable = {}
target_python_version: Optional[Version] = Version(platform.python_version())
target_formatter: Optional[str] = None
output_format: OutputFormat = OutputFormat.fixit
output_template: str = ""

def process_subpath(
subpath: Path,
Expand Down Expand Up @@ -483,6 +488,17 @@ def process_subpath(
else:
enable_root_import = True

if value := data.pop("output-format", ""):
try:
output_format = OutputFormat(value)
except ValueError as e:
raise ConfigError(
"output-format: unknown value {value!r}", config=config
) from e

if value := data.pop("output-template", ""):
output_template = value

process_subpath(
config.path.parent,
enable=get_sequence(config, "enable"),
Expand Down Expand Up @@ -524,16 +540,21 @@ def process_subpath(
options=rule_options,
python_version=target_python_version,
formatter=target_formatter,
output_format=output_format,
output_template=output_template,
)


def generate_config(
path: Path, root: Optional[Path] = None, *, options: Optional[Options] = None
path: Optional[Path] = None,
root: Optional[Path] = None,
*,
options: Optional[Options] = None,
) -> Config:
"""
Given a file path, walk upwards looking for and applying cascading configs
"""
path = path.resolve()
path = (path or Path.cwd()).resolve()

if root is not None:
root = root.resolve()
Expand All @@ -554,4 +575,10 @@ def generate_config(
config.enable = list(options.rules)
config.disable = []

if options.output_format:
config.output_format = options.output_format

if options.output_template:
config.output_template = options.output_template

return config
18 changes: 16 additions & 2 deletions src/fixit/ftypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import platform
import re
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import (
Any,
Expand Down Expand Up @@ -52,6 +53,13 @@
VisitHook = Callable[[str], ContextManager[None]]


class OutputFormat(str, Enum):
custom = "custom"
fixit = "fixit"
# json = "json" # TODO
vscode = "vscode"


@dataclass(frozen=True)
class Invalid:
code: str
Expand Down Expand Up @@ -177,10 +185,12 @@ class Options:
Command-line options to affect runtime behavior
"""

debug: Optional[bool]
config_file: Optional[Path]
debug: Optional[bool] = None
config_file: Optional[Path] = None
tags: Optional[Tags] = None
rules: Sequence[QualifiedRule] = ()
output_format: Optional[OutputFormat] = None
output_template: str = ""


@dataclass
Expand Down Expand Up @@ -223,6 +233,10 @@ class Config:
# post-run processing
formatter: Optional[str] = None

# output formatting options
output_format: OutputFormat = OutputFormat.fixit
output_template: str = ""

def __post_init__(self) -> None:
self.path = self.path.resolve()
self.root = self.root.resolve()
Expand Down
Loading

0 comments on commit a63d6b2

Please sign in to comment.