Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make format of fixit's terminal output configurable #437

Merged
merged 5 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading