mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-10-18 04:20:32 -04:00
133 lines
4.4 KiB
Python
133 lines
4.4 KiB
Python
#!/usr/bin/env python
|
|
# License: GPLv3 Copyright: 2025, un_pogaz <un.pogaz@gmail.com>
|
|
|
|
import os
|
|
from contextlib import suppress
|
|
from typing import NamedTuple
|
|
|
|
from setup import Command
|
|
|
|
HOOK_TEMPLATE = '''\
|
|
#!/usr/bin/env -S calibre-debug -e -- --
|
|
# File generated by calibre "setup.py git_hooks"
|
|
|
|
import os
|
|
import runpy
|
|
import sys
|
|
base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
sys.argv[0] = os.path.basename({file!r})
|
|
runpy.run_path(os.path.join(base, 'setup', {file!r}), run_name='__main__')
|
|
'''
|
|
|
|
|
|
class Hook(NamedTuple):
|
|
name: str
|
|
file: str
|
|
default: bool = True
|
|
|
|
|
|
HOOKS = {h.name:h for h in (
|
|
Hook('post-checkout', 'git_post_checkout_hook.py'),
|
|
Hook('post-rewrite', 'git_post_rewrite_hook.py'),
|
|
Hook('pre-commit', 'git_pre_commit_hook.py'),
|
|
# disable by default, because except Kovid, nobody can run this hook
|
|
Hook('commit-msg', 'git_commit_msg_hook.py', False),
|
|
)}
|
|
|
|
DEFAULT = ','.join(sorted(h.name for h in HOOKS.values() if h.default))
|
|
AVAILABLES = ', '.join(sorted(h for h in HOOKS))
|
|
|
|
|
|
class GitHooks(Command):
|
|
description = 'Install/uninstall git hooks'
|
|
|
|
def add_options(self, parser):
|
|
parser.add_option('-n', '--name', default=DEFAULT,
|
|
help='Name(s) of the hook to install, separated by commas. '
|
|
f'Default: "{DEFAULT}". Hooks available: {AVAILABLES}')
|
|
parser.add_option('-u', '--uninstall', default=False, action='store_true',
|
|
help='Uninstall the selected hooks')
|
|
parser.add_option('-f', '--force', default=False, action='store_true',
|
|
help='Force the operations on the hooks')
|
|
|
|
def run(self, opts):
|
|
self.force = opts.force
|
|
self.names = []
|
|
|
|
invalides = []
|
|
for candidate in sorted(c.strip().lower() for c in opts.name.split(',')):
|
|
if not candidate:
|
|
continue
|
|
if candidate not in HOOKS:
|
|
invalides.append(candidate)
|
|
else:
|
|
self.names.append(candidate)
|
|
|
|
if invalides:
|
|
self.info('Info: The following hook names are not recognized:', ', '.join(invalides))
|
|
if not self.names:
|
|
self.info('No supported hook names recognized.')
|
|
return
|
|
|
|
if opts.uninstall:
|
|
self.uninstall()
|
|
else:
|
|
self.install()
|
|
|
|
def _parse_template(self, hook_name):
|
|
base_hooks = os.path.join(os.path.dirname(self.SRC), '.git', 'hooks')
|
|
hook = HOOKS[hook_name]
|
|
path = self.j(base_hooks, hook.name)
|
|
script = HOOK_TEMPLATE.format(file=hook.file)
|
|
return path, script
|
|
|
|
def install(self):
|
|
self.info('Installing the hooks:', ', '.join(self.names))
|
|
for candidate in self.names:
|
|
path, script = self._parse_template(candidate)
|
|
|
|
if self.e(path):
|
|
with open(path, 'rb') as f:
|
|
previous = f.read().decode('utf-8')
|
|
msg = f'{candidate}: a non-calibre hook is installed.'
|
|
if previous == script:
|
|
self.info(f'{candidate}: installed.')
|
|
continue
|
|
elif self.force:
|
|
self.info(msg, 'Force installation.')
|
|
else:
|
|
self.info(msg, 'Skip installation.')
|
|
continue
|
|
|
|
self.info(f'{candidate}: installed.')
|
|
with suppress(OSError):
|
|
os.remove(path) # remove if symlink
|
|
with open(path, 'wb') as f:
|
|
f.write(script.encode('utf-8'))
|
|
try:
|
|
os.chmod(path, 0o744, follow_symlinks=False)
|
|
except NotImplementedError: # old python on windows
|
|
os.chmod(path, 0o744)
|
|
|
|
def uninstall(self):
|
|
self.info('Uninstalling the hooks:', ', '.join(self.names))
|
|
for candidate in self.names:
|
|
path, script = self._parse_template(candidate)
|
|
|
|
if not self.e(path):
|
|
self.info(f'{candidate}: no hook to unistall.')
|
|
continue
|
|
|
|
with open(path, 'rb') as f:
|
|
previous = f.read().decode('utf-8')
|
|
msg = f'{candidate}: a non-calibre hook is installed.'
|
|
if previous == script:
|
|
self.info(f'{candidate}: unistalled.')
|
|
elif self.force:
|
|
self.info(msg, 'Force unistallation.')
|
|
else:
|
|
self.info(msg, 'Skip unistallation.')
|
|
continue
|
|
|
|
os.remove(path)
|