diff --git a/.coveragerc b/.coveragerc index 14fb527e..7cf6cfae 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,7 +7,6 @@ omit = setup.py # Don't complain if non-runnable code isn't run */__main__.py - pre_commit/color_windows.py pre_commit/resources/* [report] diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 46ab3cd0..43e2c8ec 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -192,19 +192,20 @@ META_HOOK_DICT = cfgv.Map( cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))), # language must be system cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'), - *([ + *( # default to the hook definition for the meta hooks cfgv.ConditionalOptional(key, cfgv.check_any, value, 'id', hook_id) for hook_id, values in _meta for key, value in values - ] + [ + ), + *( # default to the "manifest" parsing cfgv.OptionalNoDefault(item.key, item.check_fn) # these will always be defaulted above if item.key in {'name', 'language', 'entry'} else item for item in MANIFEST_HOOK_DICT.items - ]), + ), ) CONFIG_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -215,11 +216,11 @@ CONFIG_HOOK_DICT = cfgv.Map( # are optional. # No defaults are provided here as the config is merged on top of the # manifest. - *[ + *( cfgv.OptionalNoDefault(item.key, item.check_fn) for item in MANIFEST_HOOK_DICT.items if item.key != 'id' - ], + ), ) CONFIG_REPO_DICT = cfgv.Map( 'Repository', 'repo', @@ -245,7 +246,7 @@ CONFIG_REPO_DICT = cfgv.Map( DEFAULT_LANGUAGE_VERSION = cfgv.Map( 'DefaultLanguageVersion', None, cfgv.NoAdditionalKeys(all_languages), - *[cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in all_languages], + *(cfgv.Optional(x, cfgv.check_string, C.DEFAULT) for x in all_languages), ) CONFIG_SCHEMA = cfgv.Map( 'Config', None, diff --git a/pre_commit/color.py b/pre_commit/color.py index fbb73434..caf4cb08 100644 --- a/pre_commit/color.py +++ b/pre_commit/color.py @@ -1,24 +1,64 @@ import os import sys -terminal_supports_color = True if sys.platform == 'win32': # pragma: no cover (windows) - from pre_commit.color_windows import enable_virtual_terminal_processing + def _enable() -> None: + from ctypes import POINTER + from ctypes import windll + from ctypes import WinError + from ctypes import WINFUNCTYPE + from ctypes.wintypes import BOOL + from ctypes.wintypes import DWORD + from ctypes.wintypes import HANDLE + + STD_OUTPUT_HANDLE = -11 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + + def bool_errcheck(result, func, args): + if not result: + raise WinError() + return args + + GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)( + ('GetStdHandle', windll.kernel32), ((1, 'nStdHandle'),), + ) + + GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))( + ('GetConsoleMode', windll.kernel32), + ((1, 'hConsoleHandle'), (2, 'lpMode')), + ) + GetConsoleMode.errcheck = bool_errcheck + + SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)( + ('SetConsoleMode', windll.kernel32), + ((1, 'hConsoleHandle'), (1, 'dwMode')), + ) + SetConsoleMode.errcheck = bool_errcheck + + # As of Windows 10, the Windows console supports (some) ANSI escape + # sequences, but it needs to be enabled using `SetConsoleMode` first. + # + # More info on the escape sequences supported: + # https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx + stdout = GetStdHandle(STD_OUTPUT_HANDLE) + flags = GetConsoleMode(stdout) + SetConsoleMode(stdout, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + try: - enable_virtual_terminal_processing() + _enable() except OSError: terminal_supports_color = False + else: + terminal_supports_color = True +else: # pragma: windows no cover + terminal_supports_color = True RED = '\033[41m' GREEN = '\033[42m' YELLOW = '\033[43;30m' TURQUOISE = '\033[46;30m' SUBTLE = '\033[2m' -NORMAL = '\033[0m' - - -class InvalidColorSetting(ValueError): - pass +NORMAL = '\033[m' def format_color(text: str, color: str, use_color_setting: bool) -> str: @@ -29,10 +69,10 @@ def format_color(text: str, color: str, use_color_setting: bool) -> str: color - The color start string use_color_setting - Whether or not to color """ - if not use_color_setting: - return text - else: + if use_color_setting: return f'{color}{text}{NORMAL}' + else: + return text COLOR_CHOICES = ('auto', 'always', 'never') @@ -45,7 +85,7 @@ def use_color(setting: str) -> bool: setting - Either `auto`, `always`, or `never` """ if setting not in COLOR_CHOICES: - raise InvalidColorSetting(setting) + raise ValueError(setting) return ( setting == 'always' or ( diff --git a/pre_commit/color_windows.py b/pre_commit/color_windows.py deleted file mode 100644 index 4cbb1341..00000000 --- a/pre_commit/color_windows.py +++ /dev/null @@ -1,49 +0,0 @@ -import sys -assert sys.platform == 'win32' - -from ctypes import POINTER # noqa: E402 -from ctypes import windll # noqa: E402 -from ctypes import WinError # noqa: E402 -from ctypes import WINFUNCTYPE # noqa: E402 -from ctypes.wintypes import BOOL # noqa: E402 -from ctypes.wintypes import DWORD # noqa: E402 -from ctypes.wintypes import HANDLE # noqa: E402 - - -STD_OUTPUT_HANDLE = -11 -ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 - - -def bool_errcheck(result, func, args): - if not result: - raise WinError() - return args - - -GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)( - ('GetStdHandle', windll.kernel32), ((1, 'nStdHandle'),), -) - -GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))( - ('GetConsoleMode', windll.kernel32), - ((1, 'hConsoleHandle'), (2, 'lpMode')), -) -GetConsoleMode.errcheck = bool_errcheck - -SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)( - ('SetConsoleMode', windll.kernel32), - ((1, 'hConsoleHandle'), (1, 'dwMode')), -) -SetConsoleMode.errcheck = bool_errcheck - - -def enable_virtual_terminal_processing(): - """As of Windows 10, the Windows console supports (some) ANSI escape - sequences, but it needs to be enabled using `SetConsoleMode` first. - - More info on the escape sequences supported: - https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx - """ - stdout = GetStdHandle(STD_OUTPUT_HANDLE) - flags = GetConsoleMode(stdout) - SetConsoleMode(stdout, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING) diff --git a/pre_commit/commands/init_templatedir.py b/pre_commit/commands/init_templatedir.py index 8ccab55d..f676fb19 100644 --- a/pre_commit/commands/init_templatedir.py +++ b/pre_commit/commands/init_templatedir.py @@ -29,7 +29,5 @@ def init_templatedir( dest = os.path.realpath(directory) if configured_path != dest: logger.warning('`init.templateDir` not set to the target directory') - logger.warning( - f'maybe `git config --global init.templateDir {dest}`?', - ) + logger.warning(f'maybe `git config --global init.templateDir {dest}`?') return 0 diff --git a/pre_commit/commands/migrate_config.py b/pre_commit/commands/migrate_config.py index 2e3a29fa..5b90b6f6 100644 --- a/pre_commit/commands/migrate_config.py +++ b/pre_commit/commands/migrate_config.py @@ -28,18 +28,17 @@ def _migrate_map(contents: str) -> str: # If they are using the "default" flow style of yaml, this operation # will yield a valid configuration try: - trial_contents = header + 'repos:\n' + rest + trial_contents = f'{header}repos:\n{rest}' ordered_load(trial_contents) contents = trial_contents except yaml.YAMLError: - contents = header + 'repos:\n' + _indent(rest) + contents = f'{header}repos:\n{_indent(rest)}' return contents def _migrate_sha_to_rev(contents: str) -> str: - reg = re.compile(r'(\n\s+)sha:') - return reg.sub(r'\1rev:', contents) + return re.sub(r'(\n\s+)sha:', r'\1rev:', contents) def migrate_config(config_file: str, quiet: bool = False) -> int: diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index 2cf213a7..ce5a06c2 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -20,7 +20,6 @@ from pre_commit import color from pre_commit import git from pre_commit import output from pre_commit.clientlib import load_config -from pre_commit.output import get_hook_message from pre_commit.repository import all_hooks from pre_commit.repository import Hook from pre_commit.repository import install_hook_envs @@ -33,6 +32,25 @@ from pre_commit.util import EnvironT logger = logging.getLogger('pre_commit') +def _start_msg(*, start: str, cols: int, end_len: int) -> str: + dots = '.' * (cols - len(start) - end_len - 1) + return f'{start}{dots}' + + +def _full_msg( + *, + start: str, + cols: int, + end_msg: str, + end_color: str, + use_color: bool, + postfix: str = '', +) -> str: + dots = '.' * (cols - len(start) - len(postfix) - len(end_msg) - 1) + end = color.format_color(end_msg, end_color, use_color) + return f'{start}{dots}{postfix}{end}\n' + + def filter_by_include_exclude( names: Collection[str], include: str, @@ -106,8 +124,8 @@ def _run_single_hook( if hook.id in skips or hook.alias in skips: output.write( - get_hook_message( - hook.name, + _full_msg( + start=hook.name, end_msg=SKIPPED, end_color=color.YELLOW, use_color=use_color, @@ -120,8 +138,8 @@ def _run_single_hook( out = b'' elif not filenames and not hook.always_run: output.write( - get_hook_message( - hook.name, + _full_msg( + start=hook.name, postfix=NO_FILES, end_msg=SKIPPED, end_color=color.TURQUOISE, @@ -135,7 +153,7 @@ def _run_single_hook( out = b'' else: # print hook and dots first in case the hook takes a while to run - output.write(get_hook_message(hook.name, end_len=6, cols=cols)) + output.write(_start_msg(start=hook.name, end_len=6, cols=cols)) diff_cmd = ('git', 'diff', '--no-ext-diff') diff_before = cmd_output_b(*diff_cmd, retcode=None) @@ -218,9 +236,8 @@ def _run_hooks( """Actually run the hooks.""" skips = _get_skips(environ) cols = _compute_cols(hooks) - filenames = _all_filenames(args) filenames = filter_by_include_exclude( - filenames, config['files'], config['exclude'], + _all_filenames(args), config['files'], config['exclude'], ) classifier = Classifier(filenames) retval = 0 diff --git a/pre_commit/commands/try_repo.py b/pre_commit/commands/try_repo.py index 5e7c667d..989a0c12 100644 --- a/pre_commit/commands/try_repo.py +++ b/pre_commit/commands/try_repo.py @@ -1,6 +1,7 @@ import argparse import logging import os.path +from typing import Optional from typing import Tuple from aspy.yaml import ordered_dump @@ -18,9 +19,9 @@ from pre_commit.xargs import xargs logger = logging.getLogger(__name__) -def _repo_ref(tmpdir: str, repo: str, ref: str) -> Tuple[str, str]: +def _repo_ref(tmpdir: str, repo: str, ref: Optional[str]) -> Tuple[str, str]: # if `ref` is explicitly passed, use it - if ref: + if ref is not None: return repo, ref ref = git.head_rev(repo) diff --git a/pre_commit/constants.py b/pre_commit/constants.py index aad7c498..0fc740b2 100644 --- a/pre_commit/constants.py +++ b/pre_commit/constants.py @@ -8,12 +8,7 @@ else: # pragma: no cover (PY38+) CONFIG_FILE = '.pre-commit-config.yaml' MANIFEST_FILE = '.pre-commit-hooks.yaml' -YAML_DUMP_KWARGS = { - 'default_flow_style': False, - # Use unicode - 'encoding': None, - 'indent': 4, -} +YAML_DUMP_KWARGS = {'default_flow_style': False, 'indent': 4} # Bump when installation changes in a backwards / forwards incompatible way INSTALLED_STATE_VERSION = '1' diff --git a/pre_commit/error_handler.py b/pre_commit/error_handler.py index 77b35698..0ea7ed3f 100644 --- a/pre_commit/error_handler.py +++ b/pre_commit/error_handler.py @@ -1,9 +1,9 @@ import contextlib +import functools import os.path import sys import traceback from typing import Generator -from typing import Optional import pre_commit.constants as C from pre_commit import output @@ -15,22 +15,13 @@ class FatalError(RuntimeError): def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None: - error_msg = b''.join(( - msg.encode(), b': ', - type(exc).__name__.encode(), b': ', - str(exc).encode(), - )) - output.write_line_b(error_msg) - store = Store() - log_path = os.path.join(store.directory, 'pre-commit.log') + error_msg = f'{msg}: {type(exc).__name__}: {exc}' + output.write_line(error_msg) + log_path = os.path.join(Store().directory, 'pre-commit.log') output.write_line(f'Check the log at {log_path}') with open(log_path, 'wb') as log: - def _log_line(s: Optional[str] = None) -> None: - output.write_line(s, stream=log) - - def _log_line_b(s: Optional[bytes] = None) -> None: - output.write_line_b(s, stream=log) + _log_line = functools.partial(output.write_line, stream=log) _log_line('### version information') _log_line() @@ -48,7 +39,7 @@ def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None: _log_line('### error information') _log_line() _log_line('```') - _log_line_b(error_msg) + _log_line(error_msg) _log_line('```') _log_line() _log_line('```') diff --git a/pre_commit/git.py b/pre_commit/git.py index fd8563f1..72a42545 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -141,7 +141,7 @@ def head_rev(remote: str) -> str: def has_diff(*args: str, repo: str = '.') -> bool: - cmd = ('git', 'diff', '--quiet', '--no-ext-diff') + args + cmd = ('git', 'diff', '--quiet', '--no-ext-diff', *args) return cmd_output_b(*cmd, cwd=repo, retcode=None)[0] == 1 diff --git a/pre_commit/languages/conda.py b/pre_commit/languages/conda.py index 6c4c786a..117a44a4 100644 --- a/pre_commit/languages/conda.py +++ b/pre_commit/languages/conda.py @@ -30,9 +30,9 @@ def get_env_patch(env: str) -> PatchesT: # seems to be used for python.exe. path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH')) if os.name == 'nt': # pragma: no cover (platform specific) - path = (env, os.pathsep) + path - path = (os.path.join(env, 'Scripts'), os.pathsep) + path - path = (os.path.join(env, 'Library', 'bin'), os.pathsep) + path + path = (env, os.pathsep, *path) + path = (os.path.join(env, 'Scripts'), os.pathsep, *path) + path = (os.path.join(env, 'Library', 'bin'), os.pathsep, *path) return ( ('PYTHONHOME', UNSET), diff --git a/pre_commit/languages/fail.py b/pre_commit/languages/fail.py index ff495c74..6d0f4e4b 100644 --- a/pre_commit/languages/fail.py +++ b/pre_commit/languages/fail.py @@ -18,6 +18,6 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> Tuple[int, bytes]: - out = hook.entry.encode() + b'\n\n' + out = f'{hook.entry}\n\n'.encode() out += b'\n'.join(f.encode() for f in file_args) + b'\n' return 1, out diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 914d8797..59568609 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -68,7 +68,7 @@ def install_environment( # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx?f=255&MSPPError=-2147217396#maxpath if sys.platform == 'win32': # pragma: no cover - envdir = '\\\\?\\' + os.path.normpath(envdir) + envdir = f'\\\\?\\{os.path.normpath(envdir)}' with clean_path_on_failure(envdir): cmd = [ sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir, @@ -83,7 +83,7 @@ def install_environment( helpers.run_setup_cmd(prefix, ('npm', 'install')) helpers.run_setup_cmd( prefix, - ('npm', 'install', '-g', '.') + additional_dependencies, + ('npm', 'install', '-g', '.', *additional_dependencies), ) diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index b9078113..8ccfb66d 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -49,9 +49,8 @@ def _find_by_py_launcher( if version.startswith('python'): num = version[len('python'):] try: - return cmd_output( - 'py', f'-{num}', '-c', 'import sys; print(sys.executable)', - )[1].strip() + cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)') + return cmd_output(*cmd)[1].strip() except CalledProcessError: pass return None diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index fb3ba931..0748856e 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -109,12 +109,14 @@ def install_environment( # Need to call this after installing to set up the shims helpers.run_setup_cmd(prefix, ('rbenv', 'rehash')) helpers.run_setup_cmd( - prefix, ('gem', 'build') + prefix.star('.gemspec'), + prefix, ('gem', 'build', *prefix.star('.gemspec')), ) helpers.run_setup_cmd( prefix, - ('gem', 'install', '--no-document') + - prefix.star('.gem') + additional_dependencies, + ( + 'gem', 'install', '--no-document', + *prefix.star('.gem'), *additional_dependencies, + ), ) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index c570e3c7..15906203 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -27,10 +27,7 @@ healthy = helpers.basic_healthy def get_env_patch(target_dir: str) -> PatchesT: return ( - ( - 'PATH', - (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH')), - ), + ('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))), ) diff --git a/pre_commit/languages/script.py b/pre_commit/languages/script.py index 2f7235c9..7f79719d 100644 --- a/pre_commit/languages/script.py +++ b/pre_commit/languages/script.py @@ -18,6 +18,5 @@ def run_hook( file_args: Sequence[str], color: bool, ) -> Tuple[int, bytes]: - cmd = hook.cmd - cmd = (hook.prefix.path(cmd[0]),) + cmd[1:] + cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:]) return helpers.run_xargs(hook, cmd, file_args, color=color) diff --git a/pre_commit/main.py b/pre_commit/main.py index eae4f909..d96b35fb 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -329,7 +329,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int: return install( args.config, store, hook_types=args.hook_types, - overwrite=args.overwrite, hooks=args.install_hooks, + overwrite=args.overwrite, + hooks=args.install_hooks, skip_on_missing_config=args.allow_missing_config, ) elif args.command == 'init-templatedir': diff --git a/pre_commit/make_archives.py b/pre_commit/make_archives.py index 5eb1eb7a..c31bcd71 100644 --- a/pre_commit/make_archives.py +++ b/pre_commit/make_archives.py @@ -34,7 +34,7 @@ def make_archive(name: str, repo: str, ref: str, destdir: str) -> str: :param text ref: Tag/SHA/branch to check out. :param text destdir: Directory to place archives in. """ - output_path = os.path.join(destdir, name + '.tar.gz') + output_path = os.path.join(destdir, f'{name}.tar.gz') with tmpdir() as tempdir: # Clone the repository to the temporary directory cmd_output_b('git', 'clone', repo, tempdir) @@ -56,9 +56,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: parser.add_argument('--dest', default='pre_commit/resources') args = parser.parse_args(argv) for archive_name, repo, ref in REPOS: - output.write_line( - f'Making {archive_name}.tar.gz for {repo}@{ref}', - ) + output.write_line(f'Making {archive_name}.tar.gz for {repo}@{ref}') make_archive(archive_name, repo, ref, args.dest) return 0 diff --git a/pre_commit/output.py b/pre_commit/output.py index b20b8ab4..24f9d846 100644 --- a/pre_commit/output.py +++ b/pre_commit/output.py @@ -4,59 +4,6 @@ from typing import Any from typing import IO from typing import Optional -from pre_commit import color - - -def get_hook_message( - start: str, - postfix: str = '', - end_msg: Optional[str] = None, - end_len: int = 0, - end_color: Optional[str] = None, - use_color: Optional[bool] = None, - cols: int = 80, -) -> str: - """Prints a message for running a hook. - - This currently supports three approaches: - - # Print `start` followed by dots, leaving 6 characters at the end - >>> print_hook_message('start', end_len=6) - start............................................................... - - # Print `start` followed by dots with the end message colored if coloring - # is specified and a newline afterwards - >>> print_hook_message( - 'start', - end_msg='end', - end_color=color.RED, - use_color=True, - ) - start...................................................................end - - # Print `start` followed by dots, followed by the `postfix` message - # uncolored, followed by the `end_msg` colored if specified and a newline - # afterwards - >>> print_hook_message( - 'start', - postfix='postfix ', - end_msg='end', - end_color=color.RED, - use_color=True, - ) - start...........................................................postfix end - """ - if end_len: - assert end_msg is None, end_msg - return start + '.' * (cols - len(start) - end_len - 1) - else: - assert end_msg is not None - assert end_color is not None - assert use_color is not None - dots = '.' * (cols - len(start) - len(postfix) - len(end_msg) - 1) - end = color.format_color(end_msg, end_color, use_color) - return f'{start}{dots}{postfix}{end}\n' - def write(s: str, stream: IO[bytes] = sys.stdout.buffer) -> None: stream.write(s.encode()) diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py index c1264da9..128a5c8d 100644 --- a/pre_commit/parse_shebang.py +++ b/pre_commit/parse_shebang.py @@ -20,8 +20,7 @@ def parse_filename(filename: str) -> Tuple[str, ...]: def find_executable( - exe: str, - _environ: Optional[Mapping[str, str]] = None, + exe: str, _environ: Optional[Mapping[str, str]] = None, ) -> Optional[str]: exe = os.path.normpath(exe) if os.sep in exe: diff --git a/pre_commit/repository.py b/pre_commit/repository.py index 9b071089..1ab9a2a9 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -32,7 +32,7 @@ def _state(additional_deps: Sequence[str]) -> object: def _state_filename(prefix: Prefix, venv: str) -> str: - return prefix.path(venv, '.install_state_v' + C.INSTALLED_STATE_VERSION) + return prefix.path(venv, f'.install_state_v{C.INSTALLED_STATE_VERSION}') def _read_state(prefix: Prefix, venv: str) -> Optional[object]: @@ -46,7 +46,7 @@ def _read_state(prefix: Prefix, venv: str) -> Optional[object]: def _write_state(prefix: Prefix, venv: str, state: object) -> None: state_filename = _state_filename(prefix, venv) - staging = state_filename + 'staging' + staging = f'{state_filename}staging' with open(staging, 'w') as state_file: state_file.write(json.dumps(state)) # Move the file into place atomically to indicate we've installed diff --git a/pre_commit/staged_files_only.py b/pre_commit/staged_files_only.py index 22608e59..09d323dc 100644 --- a/pre_commit/staged_files_only.py +++ b/pre_commit/staged_files_only.py @@ -50,9 +50,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]: patch_filename = f'patch{int(time.time())}' patch_filename = os.path.join(patch_dir, patch_filename) logger.warning('Unstaged files detected.') - logger.info( - f'Stashing unstaged files to {patch_filename}.', - ) + logger.info(f'Stashing unstaged files to {patch_filename}.') # Save the current unstaged changes as a patch os.makedirs(patch_dir, exist_ok=True) with open(patch_filename, 'wb') as patch_file: diff --git a/tests/color_test.py b/tests/color_test.py index 50c07d7e..98b39c1e 100644 --- a/tests/color_test.py +++ b/tests/color_test.py @@ -6,13 +6,12 @@ import pytest from pre_commit import envcontext from pre_commit.color import format_color from pre_commit.color import GREEN -from pre_commit.color import InvalidColorSetting from pre_commit.color import use_color @pytest.mark.parametrize( ('in_text', 'in_color', 'in_use_color', 'expected'), ( - ('foo', GREEN, True, f'{GREEN}foo\033[0m'), + ('foo', GREEN, True, f'{GREEN}foo\033[m'), ('foo', GREEN, False, 'foo'), ), ) @@ -56,5 +55,5 @@ def test_use_color_dumb_term(): def test_use_color_raises_if_given_shenanigans(): - with pytest.raises(InvalidColorSetting): + with pytest.raises(ValueError): use_color('herpaderp') diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index c611bfb6..562293db 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -34,7 +34,7 @@ def test_is_script(): def test_is_previous_pre_commit(tmpdir): f = tmpdir.join('foo') - f.write(PRIOR_HASHES[0] + '\n') + f.write(f'{PRIOR_HASHES[0]}\n') assert is_our_script(f.strpath) @@ -129,11 +129,11 @@ FILES_CHANGED = ( NORMAL_PRE_COMMIT_RUN = re.compile( - r'^\[INFO\] Initializing environment for .+\.\n' - r'Bash hook\.+Passed\n' - r'\[master [a-f0-9]{7}\] commit!\n' + - FILES_CHANGED + - r' create mode 100644 foo\n$', + fr'^\[INFO\] Initializing environment for .+\.\n' + fr'Bash hook\.+Passed\n' + fr'\[master [a-f0-9]{{7}}\] commit!\n' + fr'{FILES_CHANGED}' + fr' create mode 100644 foo\n$', ) @@ -296,10 +296,10 @@ def test_failing_hooks_returns_nonzero(tempdir_factory, store): EXISTING_COMMIT_RUN = re.compile( - r'^legacy hook\n' - r'\[master [a-f0-9]{7}\] commit!\n' + - FILES_CHANGED + - r' create mode 100644 baz\n$', + fr'^legacy hook\n' + fr'\[master [a-f0-9]{{7}}\] commit!\n' + fr'{FILES_CHANGED}' + fr' create mode 100644 baz\n$', ) @@ -453,10 +453,10 @@ def test_uninstall_doesnt_remove_not_our_hooks(in_git_dir): PRE_INSTALLED = re.compile( - r'Bash hook\.+Passed\n' - r'\[master [a-f0-9]{7}\] commit!\n' + - FILES_CHANGED + - r' create mode 100644 foo\n$', + fr'Bash hook\.+Passed\n' + fr'\[master [a-f0-9]{{7}}\] commit!\n' + fr'{FILES_CHANGED}' + fr' create mode 100644 foo\n$', ) diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index 1ed866bc..d2e2f236 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -7,10 +7,13 @@ from unittest import mock import pytest import pre_commit.constants as C +from pre_commit import color from pre_commit.commands.install_uninstall import install from pre_commit.commands.run import _compute_cols +from pre_commit.commands.run import _full_msg from pre_commit.commands.run import _get_skips from pre_commit.commands.run import _has_unmerged_paths +from pre_commit.commands.run import _start_msg from pre_commit.commands.run import Classifier from pre_commit.commands.run import filter_by_include_exclude from pre_commit.commands.run import run @@ -29,6 +32,62 @@ from testing.util import git_commit from testing.util import run_opts +def test_start_msg(): + ret = _start_msg(start='start', end_len=5, cols=15) + # 4 dots: 15 - 5 - 5 - 1 + assert ret == 'start....' + + +def test_full_msg(): + ret = _full_msg( + start='start', + end_msg='end', + end_color='', + use_color=False, + cols=15, + ) + # 6 dots: 15 - 5 - 3 - 1 + assert ret == 'start......end\n' + + +def test_full_msg_with_color(): + ret = _full_msg( + start='start', + end_msg='end', + end_color=color.RED, + use_color=True, + cols=15, + ) + # 6 dots: 15 - 5 - 3 - 1 + assert ret == f'start......{color.RED}end{color.NORMAL}\n' + + +def test_full_msg_with_postfix(): + ret = _full_msg( + start='start', + postfix='post ', + end_msg='end', + end_color='', + use_color=False, + cols=20, + ) + # 6 dots: 20 - 5 - 5 - 3 - 1 + assert ret == 'start......post end\n' + + +def test_full_msg_postfix_not_colored(): + ret = _full_msg( + start='start', + postfix='post ', + end_msg='end', + end_color=color.RED, + use_color=True, + cols=20, + ) + # 6 dots: 20 - 5 - 5 - 3 - 1 + assert ret == f'start......post {color.RED}end{color.NORMAL}\n' + + @pytest.fixture def repo_with_passing_hook(tempdir_factory): git_path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') @@ -173,7 +232,7 @@ def test_global_exclude(cap_out, store, in_git_dir): ret, printed = _do_run(cap_out, store, str(in_git_dir), opts) assert ret == 0 # Does not contain foo.py since it was excluded - assert printed.startswith(b'identity' + b'.' * 65 + b'Passed\n') + assert printed.startswith(f'identity{"." * 65}Passed\n'.encode()) assert printed.endswith(b'\n\n.pre-commit-config.yaml\nbar.py\n\n') @@ -190,7 +249,7 @@ def test_global_files(cap_out, store, in_git_dir): ret, printed = _do_run(cap_out, store, str(in_git_dir), opts) assert ret == 0 # Does not contain foo.py since it was excluded - assert printed.startswith(b'identity' + b'.' * 65 + b'Passed\n') + assert printed.startswith(f'identity{"." * 65}Passed\n'.encode()) assert printed.endswith(b'\n\nbar.py\n\n') diff --git a/tests/commands/try_repo_test.py b/tests/commands/try_repo_test.py index fca0f3dd..d3ec3fda 100644 --- a/tests/commands/try_repo_test.py +++ b/tests/commands/try_repo_test.py @@ -21,7 +21,7 @@ def try_repo_opts(repo, ref=None, **kwargs): def _get_out(cap_out): out = re.sub(r'\[INFO\].+\n', '', cap_out.get()) - start, using_config, config, rest = out.split('=' * 79 + '\n') + start, using_config, config, rest = out.split(f'{"=" * 79}\n') assert using_config == 'Using config:\n' return start, config, rest diff --git a/tests/error_handler_test.py b/tests/error_handler_test.py index 8fa41a70..a8626f73 100644 --- a/tests/error_handler_test.py +++ b/tests/error_handler_test.py @@ -140,7 +140,6 @@ def test_error_handler_no_tty(tempdir_factory): ret, out, _ = cmd_output_mocked_pre_commit_home( sys.executable, '-c', - 'from __future__ import unicode_literals\n' 'from pre_commit.error_handler import error_handler\n' 'with error_handler():\n' ' raise ValueError("\\u2603")\n', diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index da48e332..19890d74 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -16,7 +16,7 @@ def test_norm_version_expanduser(): expected_path = fr'{home}\python343' else: # pragma: windows no cover path = '~/.pyenv/versions/3.4.3/bin/python' - expected_path = home + '/.pyenv/versions/3.4.3/bin/python' + expected_path = f'{home}/.pyenv/versions/3.4.3/bin/python' result = python.norm_version(path) assert result == expected_path diff --git a/tests/logging_handler_test.py b/tests/logging_handler_test.py index e1506d49..fe68593b 100644 --- a/tests/logging_handler_test.py +++ b/tests/logging_handler_test.py @@ -12,7 +12,7 @@ def test_logging_handler_color(cap_out): handler = LoggingHandler(True) handler.emit(_log_record('hi', logging.WARNING)) ret = cap_out.get() - assert ret == color.YELLOW + '[WARNING]' + color.NORMAL + ' hi\n' + assert ret == f'{color.YELLOW}[WARNING]{color.NORMAL} hi\n' def test_logging_handler_no_color(cap_out): diff --git a/tests/output_test.py b/tests/output_test.py index e56c5b74..1cdacbbc 100644 --- a/tests/output_test.py +++ b/tests/output_test.py @@ -1,85 +1,9 @@ -from unittest import mock +import io -import pytest - -from pre_commit import color from pre_commit import output -@pytest.mark.parametrize( - 'kwargs', - ( - # both end_msg and end_len - {'end_msg': 'end', 'end_len': 1, 'end_color': '', 'use_color': True}, - # Neither end_msg nor end_len - {}, - # Neither color option for end_msg - {'end_msg': 'end'}, - # No use_color for end_msg - {'end_msg': 'end', 'end_color': ''}, - # No end_color for end_msg - {'end_msg': 'end', 'use_color': ''}, - ), -) -def test_get_hook_message_raises(kwargs): - with pytest.raises(AssertionError): - output.get_hook_message('start', **kwargs) - - -def test_case_with_end_len(): - ret = output.get_hook_message('start', end_len=5, cols=15) - assert ret == 'start' + '.' * 4 - - -def test_case_with_end_msg(): - ret = output.get_hook_message( - 'start', - end_msg='end', - end_color='', - use_color=False, - cols=15, - ) - assert ret == 'start' + '.' * 6 + 'end' + '\n' - - -def test_case_with_end_msg_using_color(): - ret = output.get_hook_message( - 'start', - end_msg='end', - end_color=color.RED, - use_color=True, - cols=15, - ) - assert ret == 'start' + '.' * 6 + color.RED + 'end' + color.NORMAL + '\n' - - -def test_case_with_postfix_message(): - ret = output.get_hook_message( - 'start', - postfix='post ', - end_msg='end', - end_color='', - use_color=False, - cols=20, - ) - assert ret == 'start' + '.' * 6 + 'post ' + 'end' + '\n' - - -def test_make_sure_postfix_is_not_colored(): - ret = output.get_hook_message( - 'start', - postfix='post ', - end_msg='end', - end_color=color.RED, - use_color=True, - cols=20, - ) - assert ret == ( - 'start' + '.' * 6 + 'post ' + color.RED + 'end' + color.NORMAL + '\n' - ) - - def test_output_write_writes(): - fake_stream = mock.Mock() - output.write('hello world', fake_stream) - assert fake_stream.write.call_count == 1 + stream = io.BytesIO() + output.write('hello world', stream) + assert stream.getvalue() == b'hello world' diff --git a/tests/staged_files_only_test.py b/tests/staged_files_only_test.py index be9de395..ddb95743 100644 --- a/tests/staged_files_only_test.py +++ b/tests/staged_files_only_test.py @@ -94,9 +94,9 @@ def test_foo_something_unstaged_diff_color_always(foo_staged, patch_dir): def test_foo_both_modify_non_conflicting(foo_staged, patch_dir): with open(foo_staged.foo_filename, 'w') as foo_file: - foo_file.write(FOO_CONTENTS + '9\n') + foo_file.write(f'{FOO_CONTENTS}9\n') - _test_foo_state(foo_staged, FOO_CONTENTS + '9\n', 'AM') + _test_foo_state(foo_staged, f'{FOO_CONTENTS}9\n', 'AM') with staged_files_only(patch_dir): _test_foo_state(foo_staged) @@ -107,7 +107,7 @@ def test_foo_both_modify_non_conflicting(foo_staged, patch_dir): _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a'), 'AM') - _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a') + '9\n', 'AM') + _test_foo_state(foo_staged, f'{FOO_CONTENTS.replace("1", "a")}9\n', 'AM') def test_foo_both_modify_conflicting(foo_staged, patch_dir):