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

Add mypy to the project #11

Merged
merged 3 commits into from
Sep 20, 2023
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
15 changes: 15 additions & 0 deletions .github/matchers/mypy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"problemMatcher": [
{
"owner": "mypy",
"pattern": [
{
"regexp": "^([^:]+):(\\d+): error: (.+)$",
"file": 1,
"line": 2,
"message": 3
}
]
}
]
}
7 changes: 4 additions & 3 deletions .github/workflows/linting.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ on:
- devel
- master

env:
NO_COLORS: true

jobs:
setup:
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -49,9 +46,13 @@ jobs:
run: |
poetry install
- name: Run tests
env:
NO_COLORS: true # Disable colors in ruff output
run: |
echo "::add-matcher::.github/matchers/mypy.json"
echo "::add-matcher::.github/matchers/ruff.json"
echo "::add-matcher::.github/matchers/unbehead.json"
poetry run -- make lint --keep-going
echo "::remove-matcher owner=unbehead::"
echo "::remove-matcher owner=ruff::"
echo "::remove-matcher owner=mypy::"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
dist/

# temporary directories
.mypy_cache/
.pytest_cache/
.ruff_cache/
__pycache__/
2 changes: 2 additions & 0 deletions .mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[mypy]
exclude = tests/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Renamed CLI `--ci` flag to `--check`.
- Removed Unicode interpuct from CLI output.
- Removed colors from CLI output when CI envvar is set.
- Added support for `.pyi` files.

## v1.1.1

Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# -- linting -------------------------------------------------------------------

.PHONY: mypy
mypy:
# FIXME: Remove flag once it's possible to disable color via envvar.
# The uncolored output is necessary on CI for the matcher to work.
mypy . --no-color-output

.PHONY: ruff
ruff:
ruff check .
Expand All @@ -9,7 +15,7 @@ unbehead:
unbehead --check

.PHONY: lint
lint: ruff unbehead
lint: mypy ruff unbehead

# -- testing -------------------------------------------------------------------

Expand Down
81 changes: 80 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ pyyaml = ">=6"
click = ">=8"

[tool.poetry.group.dev.dependencies]
mypy = "^1.5.1"
pytest = "^7.4.0"
ruff = "^0.0.280"
types-pyyaml = "^6.0.12.11"

[build-system]
requires = ["poetry-core"]
Expand Down
59 changes: 28 additions & 31 deletions src/unbeheader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,32 @@

import re

# Dictionary listing the files for which to change the header.
# The key is the extension of the file (without the dot) and the value is another
# dictionary containing two keys:
# - 'regex' : A regular expression matching comments in the given file type
# - 'comments': A dictionary with the comment characters to add to the header.
# There must be a `comment_start` inserted before the header,
# `comment_middle` inserted at the beginning of each line except the
# first and last one, and `comment_end` inserted at the end of the
# header.
SUPPORTED_FILES = {
'py': {
'regex': re.compile(r'((^#|[\r\n]#).*)*'),
'comments': {'comment_start': '#', 'comment_middle': '#', 'comment_end': ''}},
'wsgi': {
'regex': re.compile(r'((^#|[\r\n]#).*)*'),
'comments': {'comment_start': '#', 'comment_middle': '#', 'comment_end': ''}},
'js': {
'regex': re.compile(r'/\*(.|[\r\n])*?\*/|((^//|[\r\n]//).*)*'),
'comments': {'comment_start': '//', 'comment_middle': '//', 'comment_end': ''}},
'jsx': {
'regex': re.compile(r'/\*(.|[\r\n])*?\*/|((^//|[\r\n]//).*)*'),
'comments': {'comment_start': '//', 'comment_middle': '//', 'comment_end': ''}},
'css': {
'regex': re.compile(r'/\*(.|[\r\n])*?\*/'),
'comments': {'comment_start': '/*', 'comment_middle': ' *', 'comment_end': ' */'}},
'scss': {
'regex': re.compile(r'/\*(.|[\r\n])*?\*/|((^//|[\r\n]//).*)*'),
'comments': {'comment_start': '//', 'comment_middle': '//', 'comment_end': ''}},
'sh': {
'regex': re.compile(r'((^#|[\r\n]#).*)*'),
'comments': {'comment_start': '#', 'comment_middle': '#', 'comment_end': ''}},
from .typing import CommentSkeleton
from .typing import SupportedFileType

SUPPORTED_FILE_TYPES: dict[str, SupportedFileType] = {
'py': SupportedFileType(
re.compile(r'((^#|[\r\n]#).*)*'),
CommentSkeleton('#', '#')),
'pyi': SupportedFileType(
re.compile(r'((^#|[\r\n]#).*)*'),
CommentSkeleton('#', '#')),
'wsgi': SupportedFileType(
re.compile(r'((^#|[\r\n]#).*)*'),
CommentSkeleton('#', '#')),
'js': SupportedFileType(
re.compile(r'/\*(.|[\r\n])*?\*/|((^//|[\r\n]//).*)*'),
CommentSkeleton('//', '//')),
'jsx': SupportedFileType(
re.compile(r'/\*(.|[\r\n])*?\*/|((^//|[\r\n]//).*)*'),
CommentSkeleton('//', '//')),
'css': SupportedFileType(
re.compile(r'/\*(.|[\r\n])*?\*/'),
CommentSkeleton('/*', ' *', ' */')),
'scss': SupportedFileType(
re.compile(r'/\*(.|[\r\n])*?\*/|((^//|[\r\n]//).*)*'),
CommentSkeleton('//', '//')),
'sh': SupportedFileType(
re.compile(r'((^#|[\r\n]#).*)*'),
CommentSkeleton('#', '#')),
}
17 changes: 9 additions & 8 deletions src/unbeheader/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
import click
from click import UsageError

from . import SUPPORTED_FILES
from . import SUPPORTED_FILE_TYPES
from .headers import update_header
from .util import cformat
from .util import is_excluded

USAGE = '''
Updates all the headers in the supported files ({supported_files}).
Updates all the headers in the supported files ({supported_file_types}).
By default, all the files tracked by git in the current repository are updated
to the current year.

You can specify a year to update to as well as a file or directory.
This will update all the supported files in the scope including those not tracked
by git. If the directory does not contain any supported files (or if the file
specified is not supported) nothing will be updated.
'''.format(supported_files=', '.join(SUPPORTED_FILES)).strip()
'''.format(supported_file_types=', '.join(SUPPORTED_FILE_TYPES)).strip()


@click.command(help=USAGE)
Expand All @@ -32,9 +32,10 @@
'prevents files from actually being updated.')
@click.option('--year', '-y', type=click.IntRange(min=1000), default=date.today().year, metavar='YEAR',
help='Indicate the target year')
@click.option('--path', '-p', type=click.Path(exists=True), help='Restrict updates to a specific file or directory')
def main(check: bool, year: int, path: str):
path = Path(path).resolve() if path else None
@click.option('--path', '-p', 'path_str', type=click.Path(exists=True),
help='Restrict updates to a specific file or directory')
def main(check: bool, year: int, path_str: str) -> None:
path = Path(path_str).resolve() if path_str else None
if path and path.is_dir():
error = _run_on_directory(path, year, check)
elif path and path.is_file():
Expand Down Expand Up @@ -89,8 +90,8 @@ def _run_on_repo(year: int, check: bool) -> bool:
git_file_paths |= set(subprocess.check_output(cmd + untracked_flags, text=True).splitlines())
# Exclude deleted files
git_file_paths -= set(subprocess.check_output(cmd + deleted_flags, text=True).splitlines())
for file_path in git_file_paths:
file_path = Path(file_path).absolute()
for file_path_str in git_file_paths:
file_path = Path(file_path_str).absolute()
if not is_excluded(file_path.parent, Path.cwd()):
if update_header(file_path, year, check):
error = True
Expand Down
10 changes: 6 additions & 4 deletions src/unbeheader/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
import click
import yaml

from .typing import ConfigDict

# The substring which must be part of a comment block in order for the comment to be updated by the header
DEFAULT_SUBSTRING = 'This file is part of'

# The name of the files containing header configuration
CONFIG_FILE_NAME = '.header.yaml'


def get_config(path: Path, end_year: int) -> dict:
def get_config(path: Path, end_year: int) -> ConfigDict:
"""Get configuration from headers files."""
config = _load_config(path)
_validate_config(config)
Expand All @@ -24,8 +26,8 @@ def get_config(path: Path, end_year: int) -> dict:
return config


def _load_config(path: Path) -> dict:
config = {}
def _load_config(path: Path) -> ConfigDict:
config: ConfigDict = {}
found = False
for dir_path in _walk_to_root(path):
check_path = dir_path / CONFIG_FILE_NAME
Expand All @@ -40,7 +42,7 @@ def _load_config(path: Path) -> dict:
return config


def _validate_config(config: dict):
def _validate_config(config: ConfigDict) -> None:
valid_keys = {'owner', 'start_year', 'substring', 'template'}
mandatory_keys = {'owner', 'template'}
config_keys = set(config)
Expand Down
Loading