More miscellaneous cleanup

This commit is contained in:
Anthony Sottile
2020-01-12 21:17:59 -08:00
parent 489d9f9926
commit df40e862f4
33 changed files with 209 additions and 296 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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('```')

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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':

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

View File

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

View File

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

View File

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

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 = 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: