Merge pull request #1282 from pre-commit/cleanup

More miscellaneous cleanup
This commit is contained in:
Anthony Sottile 2020-01-13 12:47:28 -08:00 committed by GitHub
commit 5a62501307
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 209 additions and 296 deletions

View file

@ -7,7 +7,6 @@ omit =
setup.py setup.py
# Don't complain if non-runnable code isn't run # Don't complain if non-runnable code isn't run
*/__main__.py */__main__.py
pre_commit/color_windows.py
pre_commit/resources/* pre_commit/resources/*
[report] [report]

View file

@ -192,19 +192,20 @@ META_HOOK_DICT = cfgv.Map(
cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))), cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))),
# language must be system # language must be system
cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'), cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'),
*([ *(
# default to the hook definition for the meta hooks # default to the hook definition for the meta hooks
cfgv.ConditionalOptional(key, cfgv.check_any, value, 'id', hook_id) cfgv.ConditionalOptional(key, cfgv.check_any, value, 'id', hook_id)
for hook_id, values in _meta for hook_id, values in _meta
for key, value in values for key, value in values
] + [ ),
*(
# default to the "manifest" parsing # default to the "manifest" parsing
cfgv.OptionalNoDefault(item.key, item.check_fn) cfgv.OptionalNoDefault(item.key, item.check_fn)
# these will always be defaulted above # these will always be defaulted above
if item.key in {'name', 'language', 'entry'} else if item.key in {'name', 'language', 'entry'} else
item item
for item in MANIFEST_HOOK_DICT.items for item in MANIFEST_HOOK_DICT.items
]), ),
) )
CONFIG_HOOK_DICT = cfgv.Map( CONFIG_HOOK_DICT = cfgv.Map(
'Hook', 'id', 'Hook', 'id',
@ -215,11 +216,11 @@ CONFIG_HOOK_DICT = cfgv.Map(
# are optional. # are optional.
# No defaults are provided here as the config is merged on top of the # No defaults are provided here as the config is merged on top of the
# manifest. # manifest.
*[ *(
cfgv.OptionalNoDefault(item.key, item.check_fn) cfgv.OptionalNoDefault(item.key, item.check_fn)
for item in MANIFEST_HOOK_DICT.items for item in MANIFEST_HOOK_DICT.items
if item.key != 'id' if item.key != 'id'
], ),
) )
CONFIG_REPO_DICT = cfgv.Map( CONFIG_REPO_DICT = cfgv.Map(
'Repository', 'repo', 'Repository', 'repo',
@ -245,7 +246,7 @@ CONFIG_REPO_DICT = cfgv.Map(
DEFAULT_LANGUAGE_VERSION = cfgv.Map( DEFAULT_LANGUAGE_VERSION = cfgv.Map(
'DefaultLanguageVersion', None, 'DefaultLanguageVersion', None,
cfgv.NoAdditionalKeys(all_languages), 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_SCHEMA = cfgv.Map(
'Config', None, 'Config', None,

View file

@ -1,24 +1,64 @@
import os import os
import sys import sys
terminal_supports_color = True
if sys.platform == 'win32': # pragma: no cover (windows) 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: try:
enable_virtual_terminal_processing() _enable()
except OSError: except OSError:
terminal_supports_color = False terminal_supports_color = False
else:
terminal_supports_color = True
else: # pragma: windows no cover
terminal_supports_color = True
RED = '\033[41m' RED = '\033[41m'
GREEN = '\033[42m' GREEN = '\033[42m'
YELLOW = '\033[43;30m' YELLOW = '\033[43;30m'
TURQUOISE = '\033[46;30m' TURQUOISE = '\033[46;30m'
SUBTLE = '\033[2m' SUBTLE = '\033[2m'
NORMAL = '\033[0m' NORMAL = '\033[m'
class InvalidColorSetting(ValueError):
pass
def format_color(text: str, color: str, use_color_setting: bool) -> str: 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 color - The color start string
use_color_setting - Whether or not to color use_color_setting - Whether or not to color
""" """
if not use_color_setting: if use_color_setting:
return text
else:
return f'{color}{text}{NORMAL}' return f'{color}{text}{NORMAL}'
else:
return text
COLOR_CHOICES = ('auto', 'always', 'never') COLOR_CHOICES = ('auto', 'always', 'never')
@ -45,7 +85,7 @@ def use_color(setting: str) -> bool:
setting - Either `auto`, `always`, or `never` setting - Either `auto`, `always`, or `never`
""" """
if setting not in COLOR_CHOICES: if setting not in COLOR_CHOICES:
raise InvalidColorSetting(setting) raise ValueError(setting)
return ( return (
setting == 'always' or ( setting == 'always' or (

View file

@ -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)

View file

@ -29,7 +29,5 @@ def init_templatedir(
dest = os.path.realpath(directory) dest = os.path.realpath(directory)
if configured_path != dest: if configured_path != dest:
logger.warning('`init.templateDir` not set to the target directory') logger.warning('`init.templateDir` not set to the target directory')
logger.warning( logger.warning(f'maybe `git config --global init.templateDir {dest}`?')
f'maybe `git config --global init.templateDir {dest}`?',
)
return 0 return 0

View file

@ -28,18 +28,17 @@ def _migrate_map(contents: str) -> str:
# If they are using the "default" flow style of yaml, this operation # If they are using the "default" flow style of yaml, this operation
# will yield a valid configuration # will yield a valid configuration
try: try:
trial_contents = header + 'repos:\n' + rest trial_contents = f'{header}repos:\n{rest}'
ordered_load(trial_contents) ordered_load(trial_contents)
contents = trial_contents contents = trial_contents
except yaml.YAMLError: except yaml.YAMLError:
contents = header + 'repos:\n' + _indent(rest) contents = f'{header}repos:\n{_indent(rest)}'
return contents return contents
def _migrate_sha_to_rev(contents: str) -> str: def _migrate_sha_to_rev(contents: str) -> str:
reg = re.compile(r'(\n\s+)sha:') return re.sub(r'(\n\s+)sha:', r'\1rev:', contents)
return reg.sub(r'\1rev:', contents)
def migrate_config(config_file: str, quiet: bool = False) -> int: def migrate_config(config_file: str, quiet: bool = False) -> int:

View file

@ -20,7 +20,6 @@ from pre_commit import color
from pre_commit import git from pre_commit import git
from pre_commit import output from pre_commit import output
from pre_commit.clientlib import load_config 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 all_hooks
from pre_commit.repository import Hook from pre_commit.repository import Hook
from pre_commit.repository import install_hook_envs from pre_commit.repository import install_hook_envs
@ -33,6 +32,25 @@ from pre_commit.util import EnvironT
logger = logging.getLogger('pre_commit') 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( def filter_by_include_exclude(
names: Collection[str], names: Collection[str],
include: str, include: str,
@ -106,8 +124,8 @@ def _run_single_hook(
if hook.id in skips or hook.alias in skips: if hook.id in skips or hook.alias in skips:
output.write( output.write(
get_hook_message( _full_msg(
hook.name, start=hook.name,
end_msg=SKIPPED, end_msg=SKIPPED,
end_color=color.YELLOW, end_color=color.YELLOW,
use_color=use_color, use_color=use_color,
@ -120,8 +138,8 @@ def _run_single_hook(
out = b'' out = b''
elif not filenames and not hook.always_run: elif not filenames and not hook.always_run:
output.write( output.write(
get_hook_message( _full_msg(
hook.name, start=hook.name,
postfix=NO_FILES, postfix=NO_FILES,
end_msg=SKIPPED, end_msg=SKIPPED,
end_color=color.TURQUOISE, end_color=color.TURQUOISE,
@ -135,7 +153,7 @@ def _run_single_hook(
out = b'' out = b''
else: else:
# print hook and dots first in case the hook takes a while to run # 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_cmd = ('git', 'diff', '--no-ext-diff')
diff_before = cmd_output_b(*diff_cmd, retcode=None) diff_before = cmd_output_b(*diff_cmd, retcode=None)
@ -218,9 +236,8 @@ def _run_hooks(
"""Actually run the hooks.""" """Actually run the hooks."""
skips = _get_skips(environ) skips = _get_skips(environ)
cols = _compute_cols(hooks) cols = _compute_cols(hooks)
filenames = _all_filenames(args)
filenames = filter_by_include_exclude( filenames = filter_by_include_exclude(
filenames, config['files'], config['exclude'], _all_filenames(args), config['files'], config['exclude'],
) )
classifier = Classifier(filenames) classifier = Classifier(filenames)
retval = 0 retval = 0

View file

@ -1,6 +1,7 @@
import argparse import argparse
import logging import logging
import os.path import os.path
from typing import Optional
from typing import Tuple from typing import Tuple
from aspy.yaml import ordered_dump from aspy.yaml import ordered_dump
@ -18,9 +19,9 @@ from pre_commit.xargs import xargs
logger = logging.getLogger(__name__) 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` is explicitly passed, use it
if ref: if ref is not None:
return repo, ref return repo, ref
ref = git.head_rev(repo) ref = git.head_rev(repo)

View file

@ -8,12 +8,7 @@ else: # pragma: no cover (PY38+)
CONFIG_FILE = '.pre-commit-config.yaml' CONFIG_FILE = '.pre-commit-config.yaml'
MANIFEST_FILE = '.pre-commit-hooks.yaml' MANIFEST_FILE = '.pre-commit-hooks.yaml'
YAML_DUMP_KWARGS = { YAML_DUMP_KWARGS = {'default_flow_style': False, 'indent': 4}
'default_flow_style': False,
# Use unicode
'encoding': None,
'indent': 4,
}
# Bump when installation changes in a backwards / forwards incompatible way # Bump when installation changes in a backwards / forwards incompatible way
INSTALLED_STATE_VERSION = '1' INSTALLED_STATE_VERSION = '1'

View file

@ -1,9 +1,9 @@
import contextlib import contextlib
import functools
import os.path import os.path
import sys import sys
import traceback import traceback
from typing import Generator from typing import Generator
from typing import Optional
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import output from pre_commit import output
@ -15,22 +15,13 @@ class FatalError(RuntimeError):
def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None: def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None:
error_msg = b''.join(( error_msg = f'{msg}: {type(exc).__name__}: {exc}'
msg.encode(), b': ', output.write_line(error_msg)
type(exc).__name__.encode(), b': ', log_path = os.path.join(Store().directory, 'pre-commit.log')
str(exc).encode(),
))
output.write_line_b(error_msg)
store = Store()
log_path = os.path.join(store.directory, 'pre-commit.log')
output.write_line(f'Check the log at {log_path}') output.write_line(f'Check the log at {log_path}')
with open(log_path, 'wb') as log: with open(log_path, 'wb') as log:
def _log_line(s: Optional[str] = None) -> None: _log_line = functools.partial(output.write_line, stream=log)
output.write_line(s, stream=log)
def _log_line_b(s: Optional[bytes] = None) -> None:
output.write_line_b(s, stream=log)
_log_line('### version information') _log_line('### version information')
_log_line() _log_line()
@ -48,7 +39,7 @@ def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None:
_log_line('### error information') _log_line('### error information')
_log_line() _log_line()
_log_line('```') _log_line('```')
_log_line_b(error_msg) _log_line(error_msg)
_log_line('```') _log_line('```')
_log_line() _log_line()
_log_line('```') _log_line('```')

View file

@ -141,7 +141,7 @@ def head_rev(remote: str) -> str:
def has_diff(*args: str, repo: str = '.') -> bool: 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 return cmd_output_b(*cmd, cwd=repo, retcode=None)[0] == 1

View file

@ -30,9 +30,9 @@ def get_env_patch(env: str) -> PatchesT:
# seems to be used for python.exe. # seems to be used for python.exe.
path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH')) path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH'))
if os.name == 'nt': # pragma: no cover (platform specific) if os.name == 'nt': # pragma: no cover (platform specific)
path = (env, os.pathsep) + path path = (env, os.pathsep, *path)
path = (os.path.join(env, 'Scripts'), os.pathsep) + path path = (os.path.join(env, 'Scripts'), os.pathsep, *path)
path = (os.path.join(env, 'Library', 'bin'), os.pathsep) + path path = (os.path.join(env, 'Library', 'bin'), os.pathsep, *path)
return ( return (
('PYTHONHOME', UNSET), ('PYTHONHOME', UNSET),

View file

@ -18,6 +18,6 @@ def run_hook(
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> 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' out += b'\n'.join(f.encode() for f in file_args) + b'\n'
return 1, out return 1, out

View file

@ -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 # 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 if sys.platform == 'win32': # pragma: no cover
envdir = '\\\\?\\' + os.path.normpath(envdir) envdir = f'\\\\?\\{os.path.normpath(envdir)}'
with clean_path_on_failure(envdir): with clean_path_on_failure(envdir):
cmd = [ cmd = [
sys.executable, '-mnodeenv', '--prebuilt', '--clean-src', envdir, 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'))
helpers.run_setup_cmd( helpers.run_setup_cmd(
prefix, prefix,
('npm', 'install', '-g', '.') + additional_dependencies, ('npm', 'install', '-g', '.', *additional_dependencies),
) )

View file

@ -49,9 +49,8 @@ def _find_by_py_launcher(
if version.startswith('python'): if version.startswith('python'):
num = version[len('python'):] num = version[len('python'):]
try: try:
return cmd_output( cmd = ('py', f'-{num}', '-c', 'import sys; print(sys.executable)')
'py', f'-{num}', '-c', 'import sys; print(sys.executable)', return cmd_output(*cmd)[1].strip()
)[1].strip()
except CalledProcessError: except CalledProcessError:
pass pass
return None return None

View file

@ -109,12 +109,14 @@ def install_environment(
# Need to call this after installing to set up the shims # Need to call this after installing to set up the shims
helpers.run_setup_cmd(prefix, ('rbenv', 'rehash')) helpers.run_setup_cmd(prefix, ('rbenv', 'rehash'))
helpers.run_setup_cmd( helpers.run_setup_cmd(
prefix, ('gem', 'build') + prefix.star('.gemspec'), prefix, ('gem', 'build', *prefix.star('.gemspec')),
) )
helpers.run_setup_cmd( helpers.run_setup_cmd(
prefix, prefix,
('gem', 'install', '--no-document') + (
prefix.star('.gem') + additional_dependencies, 'gem', 'install', '--no-document',
*prefix.star('.gem'), *additional_dependencies,
),
) )

View file

@ -27,10 +27,7 @@ healthy = helpers.basic_healthy
def get_env_patch(target_dir: str) -> PatchesT: def get_env_patch(target_dir: str) -> PatchesT:
return ( return (
( ('PATH', (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH'))),
'PATH',
(os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH')),
),
) )

View file

@ -18,6 +18,5 @@ def run_hook(
file_args: Sequence[str], file_args: Sequence[str],
color: bool, color: bool,
) -> Tuple[int, bytes]: ) -> Tuple[int, bytes]:
cmd = hook.cmd cmd = (hook.prefix.path(hook.cmd[0]), *hook.cmd[1:])
cmd = (hook.prefix.path(cmd[0]),) + cmd[1:]
return helpers.run_xargs(hook, cmd, file_args, color=color) return helpers.run_xargs(hook, cmd, file_args, color=color)

View file

@ -329,7 +329,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
return install( return install(
args.config, store, args.config, store,
hook_types=args.hook_types, 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, skip_on_missing_config=args.allow_missing_config,
) )
elif args.command == 'init-templatedir': elif args.command == 'init-templatedir':

View file

@ -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 ref: Tag/SHA/branch to check out.
:param text destdir: Directory to place archives in. :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: with tmpdir() as tempdir:
# Clone the repository to the temporary directory # Clone the repository to the temporary directory
cmd_output_b('git', 'clone', repo, tempdir) 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') parser.add_argument('--dest', default='pre_commit/resources')
args = parser.parse_args(argv) args = parser.parse_args(argv)
for archive_name, repo, ref in REPOS: for archive_name, repo, ref in REPOS:
output.write_line( output.write_line(f'Making {archive_name}.tar.gz for {repo}@{ref}')
f'Making {archive_name}.tar.gz for {repo}@{ref}',
)
make_archive(archive_name, repo, ref, args.dest) make_archive(archive_name, repo, ref, args.dest)
return 0 return 0

View file

@ -4,59 +4,6 @@ from typing import Any
from typing import IO from typing import IO
from typing import Optional 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: def write(s: str, stream: IO[bytes] = sys.stdout.buffer) -> None:
stream.write(s.encode()) stream.write(s.encode())

View file

@ -20,8 +20,7 @@ def parse_filename(filename: str) -> Tuple[str, ...]:
def find_executable( def find_executable(
exe: str, exe: str, _environ: Optional[Mapping[str, str]] = None,
_environ: Optional[Mapping[str, str]] = None,
) -> Optional[str]: ) -> Optional[str]:
exe = os.path.normpath(exe) exe = os.path.normpath(exe)
if os.sep in exe: if os.sep in exe:

View file

@ -32,7 +32,7 @@ def _state(additional_deps: Sequence[str]) -> object:
def _state_filename(prefix: Prefix, venv: str) -> str: 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]: 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: def _write_state(prefix: Prefix, venv: str, state: object) -> None:
state_filename = _state_filename(prefix, venv) state_filename = _state_filename(prefix, venv)
staging = state_filename + 'staging' staging = f'{state_filename}staging'
with open(staging, 'w') as state_file: with open(staging, 'w') as state_file:
state_file.write(json.dumps(state)) state_file.write(json.dumps(state))
# Move the file into place atomically to indicate we've installed # Move the file into place atomically to indicate we've installed

View file

@ -50,9 +50,7 @@ def _unstaged_changes_cleared(patch_dir: str) -> Generator[None, None, None]:
patch_filename = f'patch{int(time.time())}' patch_filename = f'patch{int(time.time())}'
patch_filename = os.path.join(patch_dir, patch_filename) patch_filename = os.path.join(patch_dir, patch_filename)
logger.warning('Unstaged files detected.') logger.warning('Unstaged files detected.')
logger.info( logger.info(f'Stashing unstaged files to {patch_filename}.')
f'Stashing unstaged files to {patch_filename}.',
)
# Save the current unstaged changes as a patch # Save the current unstaged changes as a patch
os.makedirs(patch_dir, exist_ok=True) os.makedirs(patch_dir, exist_ok=True)
with open(patch_filename, 'wb') as patch_file: with open(patch_filename, 'wb') as patch_file:

View file

@ -6,13 +6,12 @@ import pytest
from pre_commit import envcontext from pre_commit import envcontext
from pre_commit.color import format_color from pre_commit.color import format_color
from pre_commit.color import GREEN from pre_commit.color import GREEN
from pre_commit.color import InvalidColorSetting
from pre_commit.color import use_color from pre_commit.color import use_color
@pytest.mark.parametrize( @pytest.mark.parametrize(
('in_text', 'in_color', 'in_use_color', 'expected'), ( ('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'), ('foo', GREEN, False, 'foo'),
), ),
) )
@ -56,5 +55,5 @@ def test_use_color_dumb_term():
def test_use_color_raises_if_given_shenanigans(): def test_use_color_raises_if_given_shenanigans():
with pytest.raises(InvalidColorSetting): with pytest.raises(ValueError):
use_color('herpaderp') use_color('herpaderp')

View file

@ -34,7 +34,7 @@ def test_is_script():
def test_is_previous_pre_commit(tmpdir): def test_is_previous_pre_commit(tmpdir):
f = tmpdir.join('foo') f = tmpdir.join('foo')
f.write(PRIOR_HASHES[0] + '\n') f.write(f'{PRIOR_HASHES[0]}\n')
assert is_our_script(f.strpath) assert is_our_script(f.strpath)
@ -129,11 +129,11 @@ FILES_CHANGED = (
NORMAL_PRE_COMMIT_RUN = re.compile( NORMAL_PRE_COMMIT_RUN = re.compile(
r'^\[INFO\] Initializing environment for .+\.\n' fr'^\[INFO\] Initializing environment for .+\.\n'
r'Bash hook\.+Passed\n' fr'Bash hook\.+Passed\n'
r'\[master [a-f0-9]{7}\] commit!\n' + fr'\[master [a-f0-9]{{7}}\] commit!\n'
FILES_CHANGED + fr'{FILES_CHANGED}'
r' create mode 100644 foo\n$', fr' create mode 100644 foo\n$',
) )
@ -296,10 +296,10 @@ def test_failing_hooks_returns_nonzero(tempdir_factory, store):
EXISTING_COMMIT_RUN = re.compile( EXISTING_COMMIT_RUN = re.compile(
r'^legacy hook\n' fr'^legacy hook\n'
r'\[master [a-f0-9]{7}\] commit!\n' + fr'\[master [a-f0-9]{{7}}\] commit!\n'
FILES_CHANGED + fr'{FILES_CHANGED}'
r' create mode 100644 baz\n$', 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( PRE_INSTALLED = re.compile(
r'Bash hook\.+Passed\n' fr'Bash hook\.+Passed\n'
r'\[master [a-f0-9]{7}\] commit!\n' + fr'\[master [a-f0-9]{{7}}\] commit!\n'
FILES_CHANGED + fr'{FILES_CHANGED}'
r' create mode 100644 foo\n$', fr' create mode 100644 foo\n$',
) )

View file

@ -7,10 +7,13 @@ from unittest import mock
import pytest import pytest
import pre_commit.constants as C import pre_commit.constants as C
from pre_commit import color
from pre_commit.commands.install_uninstall import install from pre_commit.commands.install_uninstall import install
from pre_commit.commands.run import _compute_cols 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 _get_skips
from pre_commit.commands.run import _has_unmerged_paths 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 Classifier
from pre_commit.commands.run import filter_by_include_exclude from pre_commit.commands.run import filter_by_include_exclude
from pre_commit.commands.run import run from pre_commit.commands.run import run
@ -29,6 +32,62 @@ from testing.util import git_commit
from testing.util import run_opts 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 @pytest.fixture
def repo_with_passing_hook(tempdir_factory): def repo_with_passing_hook(tempdir_factory):
git_path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') 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) ret, printed = _do_run(cap_out, store, str(in_git_dir), opts)
assert ret == 0 assert ret == 0
# Does not contain foo.py since it was excluded # 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') 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) ret, printed = _do_run(cap_out, store, str(in_git_dir), opts)
assert ret == 0 assert ret == 0
# Does not contain foo.py since it was excluded # 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') assert printed.endswith(b'\n\nbar.py\n\n')

View file

@ -21,7 +21,7 @@ def try_repo_opts(repo, ref=None, **kwargs):
def _get_out(cap_out): def _get_out(cap_out):
out = re.sub(r'\[INFO\].+\n', '', cap_out.get()) 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' assert using_config == 'Using config:\n'
return start, config, rest return start, config, rest

View file

@ -140,7 +140,6 @@ def test_error_handler_no_tty(tempdir_factory):
ret, out, _ = cmd_output_mocked_pre_commit_home( ret, out, _ = cmd_output_mocked_pre_commit_home(
sys.executable, sys.executable,
'-c', '-c',
'from __future__ import unicode_literals\n'
'from pre_commit.error_handler import error_handler\n' 'from pre_commit.error_handler import error_handler\n'
'with error_handler():\n' 'with error_handler():\n'
' raise ValueError("\\u2603")\n', ' raise ValueError("\\u2603")\n',

View file

@ -16,7 +16,7 @@ def test_norm_version_expanduser():
expected_path = fr'{home}\python343' expected_path = fr'{home}\python343'
else: # pragma: windows no cover else: # pragma: windows no cover
path = '~/.pyenv/versions/3.4.3/bin/python' 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) result = python.norm_version(path)
assert result == expected_path assert result == expected_path

View file

@ -12,7 +12,7 @@ def test_logging_handler_color(cap_out):
handler = LoggingHandler(True) handler = LoggingHandler(True)
handler.emit(_log_record('hi', logging.WARNING)) handler.emit(_log_record('hi', logging.WARNING))
ret = cap_out.get() 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): def test_logging_handler_no_color(cap_out):

View file

@ -1,85 +1,9 @@
from unittest import mock import io
import pytest
from pre_commit import color
from pre_commit import output 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(): def test_output_write_writes():
fake_stream = mock.Mock() stream = io.BytesIO()
output.write('hello world', fake_stream) output.write('hello world', stream)
assert fake_stream.write.call_count == 1 assert stream.getvalue() == b'hello world'

View file

@ -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): def test_foo_both_modify_non_conflicting(foo_staged, patch_dir):
with open(foo_staged.foo_filename, 'w') as foo_file: 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): with staged_files_only(patch_dir):
_test_foo_state(foo_staged) _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'), '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): def test_foo_both_modify_conflicting(foo_staged, patch_dir):