Skip to content

Commit

Permalink
refactor output format and config handling for cwd, more tests, rewor…
Browse files Browse the repository at this point in the history
…d docs
  • Loading branch information
amyreese committed Jul 16, 2024
1 parent 88fc8e5 commit 860be3d
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 167 deletions.
29 changes: 16 additions & 13 deletions docs/guide/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,40 +152,43 @@ The main configuration table.
:class:`~fixit.Formatter` interface.

.. attribute:: output-format
:type: Literal['fixit', 'vscode', 'custom']
:type: str

Choose one of the presets for terminal output formatting.
Fixit only reads this option from the current working directory or from an explicitly specified config file.

Defaults to fixit's output style.
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:

- ``fixit``: Fixit's default output format
- ``vscode``: A format that works well with Visual Studio Code
- ``custom``: Use your own format via :attr:`output-template` config option
- ``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.

.. 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 ``output-format = 'custom'``.
Like :attr:`output-format`, this config variable is only read from the current working directory.
Only active with :attr:`output-format` set to ``custom``.

Defaults to ``{path}@{start_line}:{start_col} {rule_name}: {message}``
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.

- ``start_line``: Start line of affected code lines.
- ``result``: Raw :class:`~fixit.Result` object.

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

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

- ``rule_name``: Name of the applied rule
- ``start_line``: Start line of affected code.

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

.. _rule-options:

Expand Down
44 changes: 22 additions & 22 deletions src/fixit/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
import trailrunner
from moreorless.click import echo_color_precomputed_diff

from .config import collect_rules, generate_config, output_formats_templates
from .config import collect_rules, generate_config
from .engine import LintRunner
from .format import format_module
from .ftypes import (
Config,
FileContent,
LintViolation,
Options,
OutputFormatTypeInput,
OutputFormat,
Result,
STDIN,
)
Expand All @@ -35,8 +35,8 @@ def print_result(
*,
show_diff: bool = False,
stderr: bool = False,
output_format_type: OutputFormatTypeInput = "fixit",
output_template: str = output_formats_templates["fixit"],
output_format: OutputFormat = OutputFormat.fixit,
output_template: str = "",
) -> int:
"""
Print linting results in a simple format designed for human eyes.
Expand All @@ -52,27 +52,31 @@ def print_result(
except ValueError:
pass

if output_format_type != "custom":
output_template = output_formats_templates[output_format_type]

if result.violation:
rule_name = result.violation.rule_name
start_line = result.violation.range.start.line
start_col = result.violation.range.start.column
message = result.violation.message
if result.violation.autofixable:
message += " (has autofix)"
click.secho(
output_template.format(

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,
start_line=start_line,
start_col=start_col,
result=result,
rule_name=rule_name,
message=message,
),
fg="yellow",
err=stderr,
)
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 Expand Up @@ -125,7 +129,7 @@ def fixit_bytes(
clean = True
for violation in runner.collect_violations(rules, config):
clean = False
fix = yield Result(path, violation=violation)
fix = yield Result(path, violation)
if fix or autofix:
pending_fixes.append(violation)

Expand All @@ -139,11 +143,7 @@ def fixit_bytes(
except Exception as error:
# TODO: this is not the right place to catch errors
LOG.debug("Exception while linting", exc_info=error)
yield Result(
path,
violation=None,
error=(error, traceback.format_exc()),
)
yield Result(path, violation=None, error=(error, traceback.format_exc()))

return None

Expand Down
38 changes: 16 additions & 22 deletions src/fixit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,15 @@
import sys
import unittest
from pathlib import Path
from typing import Dict, get_args, Optional, Sequence, Set, Type
from typing import Dict, Optional, Sequence, Set, Type

import click

from fixit import __version__

from .api import fixit_paths, print_result
from .config import collect_rules, generate_config, get_cwd_config, parse_rule
from .ftypes import (
Config,
LSPOptions,
Options,
OutputFormatTypeInput,
QualifiedRule,
Tags,
)
from .config import collect_rules, generate_config, parse_rule
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 @@ -82,24 +75,25 @@ def f(v: int) -> str:
@click.option(
"--output-format",
"-o",
type=click.Choice(get_args(OutputFormatTypeInput), case_sensitive=False),
type=click.Choice([o.name for o in OutputFormat], case_sensitive=False),
show_choices=True,
default=None,
help="Select output format type [fixit, vscode, json, custom]",
help="Select output format type",
)
@click.option(
"--output-template",
type=str,
default=None,
help="Python format Template to use with output format 'custom'",
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[OutputFormatTypeInput],
output_template: Optional[str],
output_format: Optional[OutputFormat],
output_template: str,
) -> None:
level = logging.WARNING
if debug is not None:
Expand Down Expand Up @@ -145,14 +139,14 @@ def lint(
visited: Set[Path] = set()
dirty: Set[Path] = set()
autofixes = 0
cwd_config = get_cwd_config(options=options)
config = generate_config(options=options)
for result in fixit_paths(paths, options=options):
visited.add(result.path)
if print_result(
result,
show_diff=diff,
output_format_type=cwd_config.output_format,
output_template=cwd_config.output_template,
output_format=config.output_format,
output_template=config.output_template,
):
dirty.add(result.path)
if result.violation:
Expand Down Expand Up @@ -208,7 +202,7 @@ def fix(
generator = capture(
fixit_paths(paths, autofix=autofix, options=options, parallel=False)
)
cwd_config = get_cwd_config(options=options)
config = generate_config(options=options)
for result in generator:
visited.add(result.path)
# for STDIN, we need STDOUT to equal the fixed content, so
Expand All @@ -217,8 +211,8 @@ def fix(
result,
show_diff=interactive or diff,
stderr=is_stdin,
output_format_type=cwd_config.output_format,
output_template=cwd_config.output_template,
output_format=config.output_format,
output_template=config.output_template,
):
dirty.add(result.path)
if autofix and result.violation and result.violation.autofixable:
Expand Down
62 changes: 22 additions & 40 deletions src/fixit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,10 @@
from .format import FORMAT_STYLES
from .ftypes import (
Config,
CwdConfig,
is_collection,
is_sequence,
Options,
OutputFormatType,
OutputFormatTypeInput,
OutputFormat,
QualifiedRule,
QualifiedRuleRegex,
RawConfig,
Expand All @@ -58,13 +56,6 @@

FIXIT_CONFIG_FILENAMES = ("fixit.toml", ".fixit.toml", "pyproject.toml")
FIXIT_LOCAL_MODULE = "fixit.local"
CWD_CONFIG_KEYS = ("output-format", "output-template")


output_formats_templates: Dict[OutputFormatType, str] = {
"fixit": "{path}@{start_line}:{start_col} {rule_name}: {message}",
"vscode": "{path}:{start_line}:{start_col} {rule_name}: {message}",
}


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -414,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 @@ -495,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 @@ -525,8 +529,7 @@ def process_subpath(
)

for key in data.keys():
if key not in CWD_CONFIG_KEYS:
log.warning("unknown configuration option %r", key)
log.warning("unknown configuration option %r", key)

return Config(
path=path,
Expand All @@ -537,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 @@ -567,32 +575,6 @@ def generate_config(
config.enable = list(options.rules)
config.disable = []

return config


def get_cwd_config(options: Optional[Options] = None) -> CwdConfig:
config = CwdConfig()
if options and options.config_file:
paths = [options.config_file]
else:
cwd = Path.cwd()
paths = locate_configs(cwd, cwd)

raw_configs = read_configs(paths)

output_format: Optional[OutputFormatTypeInput] = None
output_template: Optional[str] = None
for raw_config in raw_configs:

if output_format is None:
output_format = raw_config.data.get("output-format")
if output_template is None:
output_template = raw_config.data.get("output-template")

config.output_format = output_format or "fixit"
config.output_template = output_template or output_formats_templates["fixit"]

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

Expand Down
Loading

0 comments on commit 860be3d

Please sign in to comment.