from __future__ import annotations
import ast
import json
from argparse import ArgumentParser
from pathlib import Path
from textwrap import dedent, indent
from typing import Iterable, Iterator
from .._colors import get_colors, highlight
from ..linter import Checker
from ._base import Command
from ._common import get_paths
TEMPLATE = ' {blue}{row}{end}:{blue}{col}{end} {magenta}{code}{end} {yellow}{text}{end}'
VALUE = ' {magenta}({value}){end}'
POINTER = '{magenta}^{end}'
[docs]class LintCommand(Command):
"""Run linter against the given files.
```bash
python3 -m deal lint project/
```
Options:
+ `--json`: output violations as [json per line][ndjson].
+ `--nocolor`: output violations in human-friendly format but without colors.
Useful for running linter on CI.
Exit code is equal to the found violations count.
See [linter][linter] documentation for more details.
[ndjson]: http://ndjson.org/
[linter]: https://deal.readthedocs.io/basic/linter.html
"""
@staticmethod
def init_parser(parser: ArgumentParser) -> None:
parser.add_argument('--json', action='store_true', help='json output')
parser.add_argument('--nocolor', action='store_true', help='colorless output')
parser.add_argument('paths', nargs='*', default='.')
def __call__(self, args) -> int:
prev = None
errors = list(self.get_errors(paths=args.paths))
colors = get_colors(args)
for error in errors:
if args.json:
self.print(json.dumps(error))
continue
# print file path
if error['path'] != prev:
self.print('{green}{path}{end}'.format(**colors, **error))
prev = error['path']
# print message
line = TEMPLATE.format(**colors, **error)
if error['value']:
line += VALUE.format(**colors, **error)
self.print(line)
# print code line
pointer = ' ' * error['col'] + POINTER.format(**colors)
content = error['content']
if not args.nocolor:
content = highlight(content)
content += '\n' + pointer
content = indent(dedent(content), prefix=' ')
self.print(content)
return len(errors)
@staticmethod
def get_errors(paths: Iterable[str]) -> Iterator[dict]:
for arg in paths:
for path in get_paths(Path(arg)):
content = path.read_text()
checker = Checker(
filename=str(path),
tree=ast.parse(content),
)
lines = content.split('\n')
for error in checker.get_errors():
yield dict(
path=str(path),
row=error.row,
col=error.col,
code=error.full_code,
text=error.text,
value=error.value,
content=lines[error.row - 1],
)