Repository: pre-commit/pre-commit
Pull request #1282 (2020-01-13 05:20:23)

More miscellaneous cleanup


Patch:
diff --git a/.coveragerc b/.coveragerc
index 14fb527..7cf6cfa 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 46ab3cd..43e2c8e 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 fbb7343..caf4cb0 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 4cbb134..0000000
--- 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 8ccab55..f676fb1 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 2e3a29f..5b90b6f 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 2cf213a..ce5a06c 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 5e7c667..989a0c1 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 aad7c49..0fc740b 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 77b3569..0ea7ed3 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 fd8563f..72a4254 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 6c4c786..117a44a 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 ff495c7..6d0f4e4 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 914d879..5956860 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 b907811..8ccfb66 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 fb3ba93..0748856 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 c570e3c..1590620 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 2f7235c..7f79719 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 eae4f90..d96b35f 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 5eb1eb7..c31bcd7 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 b20b8ab..24f9d84 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 c1264da..128a5c8 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 9b07108..1ab9a2a 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 22608e5..09d323d 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:


Test patch:
diff --git a/tests/color_test.py b/tests/color_test.py
index 50c07d7..98b39c1 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 c611bfb..562293d 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 1ed866b..d2e2f23 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 fca0f3d..d3ec3fd 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 8fa41a7..a8626f7 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 da48e33..19890d7 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 e1506d4..fe68593 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 e56c5b7..1cdacbb 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 be9de39..ddb9574 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):