Merge branch 'refactor-fstring' of https://github.com/page-down/kitty

This commit is contained in:
Kovid Goyal 2022-01-30 09:26:17 +05:30
commit 2e790a119a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
56 changed files with 231 additions and 242 deletions

View File

@ -48,7 +48,7 @@ def install_deps():
def build_kitty(): def build_kitty():
python = shutil.which('python3') if is_bundle else sys.executable python = shutil.which('python3') if is_bundle else sys.executable
cmd = '{} setup.py build --verbose'.format(python) cmd = f'{python} setup.py build --verbose'
if os.environ.get('KITTY_SANITIZE') == '1': if os.environ.get('KITTY_SANITIZE') == '1':
cmd += ' --debug --sanitize' cmd += ' --debug --sanitize'
run(cmd) run(cmd)
@ -59,8 +59,8 @@ def test_kitty():
def package_kitty(): def package_kitty():
py = 'python3' if is_macos else 'python' python = 'python3' if is_macos else 'python'
run(py + ' setup.py linux-package --update-check-interval=0 --verbose') run(f'{python} setup.py linux-package --update-check-interval=0 --verbose')
if is_macos: if is_macos:
run('python3 setup.py kitty.app --update-check-interval=0 --verbose') run('python3 setup.py kitty.app --update-check-interval=0 --verbose')
run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"') run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"')
@ -76,11 +76,11 @@ def replace_in_file(path, src, dest):
def setup_bundle_env(): def setup_bundle_env():
global SW global SW
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw') os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
os.environ['PKG_CONFIG_PATH'] = SW + '/lib/pkgconfig' os.environ['PKG_CONFIG_PATH'] = os.path.join(SW, 'lib', 'pkgconfig')
if is_macos: if is_macos:
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH']) os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
else: else:
os.environ['LD_LIBRARY_PATH'] = SW + '/lib' os.environ['LD_LIBRARY_PATH'] = os.path.join(SW, 'lib')
os.environ['PYTHONHOME'] = SW os.environ['PYTHONHOME'] = SW
os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH']) os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH'])
@ -111,7 +111,7 @@ def main():
setup_bundle_env() setup_bundle_env()
else: else:
if not is_macos and 'pythonLocation' in os.environ: if not is_macos and 'pythonLocation' in os.environ:
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation']) os.environ['LD_LIBRARY_PATH'] = os.path.join(os.environ['pythonLocation'], 'lib')
action = sys.argv[-1] action = sys.argv[-1]
if action in ('build', 'package'): if action in ('build', 'package'):
install_deps() install_deps()
@ -122,7 +122,7 @@ def main():
elif action == 'test': elif action == 'test':
test_kitty() test_kitty()
else: else:
raise SystemExit('Unknown action: ' + action) raise SystemExit(f'Unknown action: {action}')
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -13,7 +13,7 @@ import tempfile
def compile_terminfo(base): def compile_terminfo(base):
with tempfile.TemporaryDirectory() as tdir: with tempfile.TemporaryDirectory() as tdir:
proc = subprocess.run(['tic', '-x', '-o' + tdir, 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE) proc = subprocess.run(['tic', '-x', f'-o{tdir}', 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE)
regex = '^"terminfo/kitty.terminfo", line [0-9]+, col [0-9]+, terminal \'xterm-kitty\': older tic versions may treat the description field as an alias$' regex = '^"terminfo/kitty.terminfo", line [0-9]+, col [0-9]+, terminal \'xterm-kitty\': older tic versions may treat the description field as an alias$'
for error in proc.stderr.decode('utf-8').splitlines(): for error in proc.stderr.decode('utf-8').splitlines():
if not re.match(regex, error): if not re.match(regex, error):

View File

@ -26,7 +26,7 @@ def initialize_constants():
kitty_constants = {} kitty_constants = {}
src = read_src_file('constants.py') src = read_src_file('constants.py')
nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src) nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src)
kitty_constants['version'] = '%s.%s.%s' % (nv.group(1), nv.group(2), nv.group(3)) kitty_constants['version'] = f'{nv.group(1)}.{nv.group(2)}.{nv.group(3)}'
kitty_constants['appname'] = re.search( kitty_constants['appname'] = re.search(
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
).group(2) ).group(2)

View File

@ -38,7 +38,7 @@ def binary_includes():
'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec' 'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec'
))) + ( ))) + (
get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2), get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
get_dll_path('python' + py_ver, 2), get_dll_path(f'python{py_ver}', 2),
) )
@ -47,7 +47,7 @@ class Env:
def __init__(self, package_dir): def __init__(self, package_dir):
self.base = package_dir self.base = package_dir
self.lib_dir = j(self.base, 'lib') self.lib_dir = j(self.base, 'lib')
self.py_dir = j(self.lib_dir, 'python' + py_ver) self.py_dir = j(self.lib_dir, f'python{py_ver}')
os.makedirs(self.py_dir) os.makedirs(self.py_dir)
self.bin_dir = j(self.base, 'bin') self.bin_dir = j(self.base, 'bin')
self.obj_dir = mkdtemp('launchers-') self.obj_dir = mkdtemp('launchers-')
@ -107,7 +107,7 @@ def add_ca_certs(env):
def copy_python(env): def copy_python(env):
print('Copying python...') print('Copying python...')
srcdir = j(PREFIX, 'lib/python' + py_ver) srcdir = j(PREFIX, f'lib/python{py_ver}')
for x in os.listdir(srcdir): for x in os.listdir(srcdir):
y = j(srcdir, x) y = j(srcdir, x)
@ -187,11 +187,11 @@ def strip_files(files, argv_max=(256 * 1024)):
def strip_binaries(files): def strip_binaries(files):
print('Stripping %d files...' % len(files)) print(f'Stripping {len(files)} files...')
before = sum(os.path.getsize(x) for x in files) before = sum(os.path.getsize(x) for x in files)
strip_files(files) strip_files(files)
after = sum(os.path.getsize(x) for x in files) after = sum(os.path.getsize(x) for x in files)
print('Stripped %.1f MB' % ((before - after) / (1024 * 1024.))) print('Stripped {:.1f} MB'.format((before - after) / (1024 * 1024.)))
def create_tarfile(env, compression_level='9'): def create_tarfile(env, compression_level='9'):
@ -203,7 +203,7 @@ def create_tarfile(env, compression_level='9'):
if err.errno != errno.ENOENT: if err.errno != errno.ENOENT:
raise raise
os.mkdir(base) os.mkdir(base)
dist = os.path.join(base, '%s-%s-%s.tar' % (kitty_constants['appname'], kitty_constants['version'], arch)) dist = os.path.join(base, f'{kitty_constants["appname"]}-{kitty_constants["version"]}-{arch}.tar')
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf: with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(env.base) os.chdir(env.base)
@ -213,13 +213,13 @@ def create_tarfile(env, compression_level='9'):
finally: finally:
os.chdir(cwd) os.chdir(cwd)
print('Compressing archive...') print('Compressing archive...')
ans = dist.rpartition('.')[0] + '.txz' ans = f'{dist.rpartition(".")[0]}.txz'
start_time = time.time() start_time = time.time()
subprocess.check_call(['xz', '--threads=0', '-f', '-' + compression_level, dist]) subprocess.check_call(['xz', '--threads=0', '-f', f'-{compression_level}', dist])
secs = time.time() - start_time secs = time.time() - start_time
print('Compressed in %d minutes %d seconds' % (secs // 60, secs % 60)) print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
os.rename(dist + '.xz', ans) os.rename(f'{dist}.xz', ans)
print('Archive %s created: %.2f MB' % ( print('Archive {} created: {:.2f} MB'.format(
os.path.basename(ans), os.stat(ans).st_size / (1024.**2))) os.path.basename(ans), os.stat(ans).st_size / (1024.**2)))

View File

@ -90,7 +90,7 @@ def strip_files(files, argv_max=(256 * 1024)):
def files_in(folder): def files_in(folder):
for record in os.walk(folder): for record in os.walk(folder):
for f in record[-1]: for f in record[-1]:
yield os.path.join(record[0], f) yield join(record[0], f)
def expand_dirs(items, exclude=lambda x: x.endswith('.so')): def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
@ -103,7 +103,7 @@ def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
def do_sign(app_dir): def do_sign(app_dir):
with current_dir(os.path.join(app_dir, 'Contents')): with current_dir(join(app_dir, 'Contents')):
# Sign all .so files # Sign all .so files
so_files = {x for x in files_in('.') if x.endswith('.so')} so_files = {x for x in files_in('.') if x.endswith('.so')}
codesign(so_files) codesign(so_files)
@ -153,7 +153,7 @@ class Freeze(object):
self.to_strip = [] self.to_strip = []
self.warnings = [] self.warnings = []
self.py_ver = py_ver self.py_ver = py_ver
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', 'python' + self.py_ver) self.python_stdlib = join(self.resources_dir, 'Python', 'lib', f'python{self.py_ver}')
self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path
self.obj_dir = mkdtemp('launchers-') self.obj_dir = mkdtemp('launchers-')
@ -177,7 +177,7 @@ class Freeze(object):
self.run_tests() self.run_tests()
# self.run_shell() # self.run_shell()
ret = self.makedmg(self.build_dir, APPNAME + '-' + VERSION) ret = self.makedmg(self.build_dir, f'{APPNAME}-{VERSION}')
return ret return ret
@ -186,7 +186,7 @@ class Freeze(object):
print('\nDownloading CA certs...') print('\nDownloading CA certs...')
from urllib.request import urlopen from urllib.request import urlopen
cdata = urlopen(kitty_constants['cacerts_url']).read() cdata = urlopen(kitty_constants['cacerts_url']).read()
dest = os.path.join(self.contents_dir, 'Resources', 'cacert.pem') dest = join(self.contents_dir, 'Resources', 'cacert.pem')
with open(dest, 'wb') as f: with open(dest, 'wb') as f:
f.write(cdata) f.write(cdata)
@ -197,7 +197,7 @@ class Freeze(object):
@flush @flush
def run_tests(self): def run_tests(self):
iv['run_tests'](os.path.join(self.contents_dir, 'MacOS', 'kitty')) iv['run_tests'](join(self.contents_dir, 'MacOS', 'kitty'))
@flush @flush
def set_id(self, path_to_lib, new_id): def set_id(self, path_to_lib, new_id):
@ -222,10 +222,10 @@ class Freeze(object):
@flush @flush
def get_local_dependencies(self, path_to_lib): def get_local_dependencies(self, path_to_lib):
for x, is_id in self.get_dependencies(path_to_lib): for x, is_id in self.get_dependencies(path_to_lib):
for y in (PREFIX + '/lib/', PREFIX + '/python/Python.framework/', '@rpath/'): for y in (f'{PREFIX}/lib/', f'{PREFIX}/python/Python.framework/', '@rpath/'):
if x.startswith(y): if x.startswith(y):
if y == PREFIX + '/python/Python.framework/': if y == f'{PREFIX}/python/Python.framework/':
y = PREFIX + '/python/' y = f'{PREFIX}/python/'
yield x, x[len(y):], is_id yield x, x[len(y):], is_id
break break
@ -239,7 +239,7 @@ class Freeze(object):
self.to_strip.append(path_to_lib) self.to_strip.append(path_to_lib)
old_mode = flipwritable(path_to_lib) old_mode = flipwritable(path_to_lib)
for dep, bname, is_id in self.get_local_dependencies(path_to_lib): for dep, bname, is_id in self.get_local_dependencies(path_to_lib):
ndep = self.FID + '/' + bname ndep = f'{self.FID}/{bname}'
self.change_dep(dep, ndep, is_id, path_to_lib) self.change_dep(dep, ndep, is_id, path_to_lib)
ldeps = list(self.get_local_dependencies(path_to_lib)) ldeps = list(self.get_local_dependencies(path_to_lib))
if ldeps: if ldeps:
@ -252,7 +252,7 @@ class Freeze(object):
@flush @flush
def add_python_framework(self): def add_python_framework(self):
print('\nAdding Python framework') print('\nAdding Python framework')
src = join(PREFIX + '/python', 'Python.framework') src = join(f'{PREFIX}/python', 'Python.framework')
x = join(self.frameworks_dir, 'Python.framework') x = join(self.frameworks_dir, 'Python.framework')
curr = os.path.realpath(join(src, 'Versions', 'Current')) curr = os.path.realpath(join(src, 'Versions', 'Current'))
currd = join(x, 'Versions', basename(curr)) currd = join(x, 'Versions', basename(curr))
@ -262,12 +262,12 @@ class Freeze(object):
shutil.copy2(join(curr, 'Python'), currd) shutil.copy2(join(curr, 'Python'), currd)
self.set_id( self.set_id(
join(currd, 'Python'), join(currd, 'Python'),
self.FID + '/Python.framework/Versions/%s/Python' % basename(curr)) f'{self.FID}/Python.framework/Versions/{basename(curr)}/Python')
# The following is needed for codesign # The following is needed for codesign
with current_dir(x): with current_dir(x):
os.symlink(basename(curr), 'Versions/Current') os.symlink(basename(curr), 'Versions/Current')
for y in ('Python', 'Resources'): for y in ('Python', 'Resources'):
os.symlink('Versions/Current/%s' % y, y) os.symlink(f'Versions/Current/{y}', y)
@flush @flush
def install_dylib(self, path, set_id=True): def install_dylib(self, path, set_id=True):
@ -275,7 +275,7 @@ class Freeze(object):
if set_id: if set_id:
self.set_id( self.set_id(
join(self.frameworks_dir, basename(path)), join(self.frameworks_dir, basename(path)),
self.FID + '/' + basename(path)) f'{self.FID}/{basename(path)}')
self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path))) self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path)))
@flush @flush
@ -291,11 +291,11 @@ class Freeze(object):
'rsync.2', 'rsync.2',
): ):
print('\nAdding', x) print('\nAdding', x)
x = 'lib%s.dylib' % x x = f'lib{x}.dylib'
src = join(PREFIX, 'lib', x) src = join(PREFIX, 'lib', x)
shutil.copy2(src, self.frameworks_dir) shutil.copy2(src, self.frameworks_dir)
dest = join(self.frameworks_dir, x) dest = join(self.frameworks_dir, x)
self.set_id(dest, self.FID + '/' + x) self.set_id(dest, f'{self.FID}/{x}')
self.fix_dependencies_in_lib(dest) self.fix_dependencies_in_lib(dest)
@flush @flush
@ -321,7 +321,7 @@ class Freeze(object):
@flush @flush
def add_stdlib(self): def add_stdlib(self):
print('\nAdding python stdlib') print('\nAdding python stdlib')
src = PREFIX + '/python/Python.framework/Versions/Current/lib/python' + self.py_ver src = f'{PREFIX}/python/Python.framework/Versions/Current/lib/python{self.py_ver}'
dest = self.python_stdlib dest = self.python_stdlib
if not os.path.exists(dest): if not os.path.exists(dest):
os.makedirs(dest) os.makedirs(dest)
@ -345,19 +345,19 @@ class Freeze(object):
kitty_dir = join(self.resources_dir, 'kitty') kitty_dir = join(self.resources_dir, 'kitty')
bases = ('kitty', 'kittens', 'kitty_tests') bases = ('kitty', 'kittens', 'kitty_tests')
for x in bases: for x in bases:
dest = os.path.join(self.python_stdlib, x) dest = join(self.python_stdlib, x)
os.rename(os.path.join(kitty_dir, x), dest) os.rename(join(kitty_dir, x), dest)
if x == 'kitty': if x == 'kitty':
shutil.rmtree(os.path.join(dest, 'launcher')) shutil.rmtree(join(dest, 'launcher'))
os.rename(os.path.join(kitty_dir, '__main__.py'), os.path.join(self.python_stdlib, 'kitty_main.py')) os.rename(join(kitty_dir, '__main__.py'), join(self.python_stdlib, 'kitty_main.py'))
shutil.rmtree(os.path.join(kitty_dir, '__pycache__')) shutil.rmtree(join(kitty_dir, '__pycache__'))
pdir = os.path.join(dirname(self.python_stdlib), 'kitty-extensions') pdir = join(dirname(self.python_stdlib), 'kitty-extensions')
os.mkdir(pdir) os.mkdir(pdir)
print('Extracting extension modules from', self.python_stdlib, 'to', pdir) print('Extracting extension modules from', self.python_stdlib, 'to', pdir)
ext_map = extract_extension_modules(self.python_stdlib, pdir) ext_map = extract_extension_modules(self.python_stdlib, pdir)
shutil.copy(os.path.join(os.path.dirname(self_dir), 'site.py'), os.path.join(self.python_stdlib, 'site.py')) shutil.copy(join(os.path.dirname(self_dir), 'site.py'), join(self.python_stdlib, 'site.py'))
for x in bases: for x in bases:
iv['sanitize_source_folder'](os.path.join(self.python_stdlib, x)) iv['sanitize_source_folder'](join(self.python_stdlib, x))
self.compile_py_modules() self.compile_py_modules()
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True) freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir]) iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
@ -434,23 +434,23 @@ class Freeze(object):
''' Copy a directory d into a dmg named volname ''' ''' Copy a directory d into a dmg named volname '''
print('\nMaking dmg...') print('\nMaking dmg...')
sys.stdout.flush() sys.stdout.flush()
destdir = os.path.join(SW, 'dist') destdir = join(SW, 'dist')
try: try:
shutil.rmtree(destdir) shutil.rmtree(destdir)
except FileNotFoundError: except FileNotFoundError:
pass pass
os.mkdir(destdir) os.mkdir(destdir)
dmg = os.path.join(destdir, volname + '.dmg') dmg = join(destdir, f'{volname}.dmg')
if os.path.exists(dmg): if os.path.exists(dmg):
os.unlink(dmg) os.unlink(dmg)
tdir = tempfile.mkdtemp() tdir = tempfile.mkdtemp()
appdir = os.path.join(tdir, os.path.basename(d)) appdir = join(tdir, os.path.basename(d))
shutil.copytree(d, appdir, symlinks=True) shutil.copytree(d, appdir, symlinks=True)
if self.sign_installers: if self.sign_installers:
with timeit() as times: with timeit() as times:
sign_app(appdir, self.notarize) sign_app(appdir, self.notarize)
print('Signing completed in %d minutes %d seconds' % tuple(times)) print('Signing completed in {} minutes {} seconds'.format(*times))
os.symlink('/Applications', os.path.join(tdir, 'Applications')) os.symlink('/Applications', join(tdir, 'Applications'))
size_in_mb = int( size_in_mb = int(
subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8') subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8')
.split()[0]) / 1024. .split()[0]) / 1024.
@ -466,10 +466,10 @@ class Freeze(object):
print('\nCreating dmg...') print('\nCreating dmg...')
with timeit() as times: with timeit() as times:
subprocess.check_call(cmd + [dmg]) subprocess.check_call(cmd + [dmg])
print('dmg created in %d minutes and %d seconds' % tuple(times)) print('dmg created in {} minutes and {} seconds'.format(*times))
shutil.rmtree(tdir) shutil.rmtree(tdir)
size = os.stat(dmg).st_size / (1024 * 1024.) size = os.stat(dmg).st_size / (1024 * 1024.)
print('\nInstaller size: %.2fMB\n' % size) print(f'\nInstaller size: {size:.2f}MB\n')
return dmg return dmg
@ -477,7 +477,7 @@ def main():
args = globals()['args'] args = globals()['args']
ext_dir = globals()['ext_dir'] ext_dir = globals()['ext_dir']
Freeze( Freeze(
os.path.join(ext_dir, kitty_constants['appname'] + '.app'), join(ext_dir, f'{kitty_constants["appname"]}.app'),
dont_strip=args.dont_strip, dont_strip=args.dont_strip,
sign_installers=args.sign_installers, sign_installers=args.sign_installers,
notarize=args.notarize, notarize=args.notarize,

View File

@ -13,7 +13,7 @@ import subprocess
import sys import sys
import time import time
from functools import partial from functools import partial
from typing import Any, Callable, Dict, Iterable, List, Match, Optional, Tuple from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
from docutils import nodes from docutils import nodes
from docutils.parsers.rst.roles import set_classes from docutils.parsers.rst.roles import set_classes
@ -28,7 +28,7 @@ kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if kitty_src not in sys.path: if kitty_src not in sys.path:
sys.path.insert(0, kitty_src) sys.path.insert(0, kitty_src)
from kitty.conf.types import Definition # noqa from kitty.conf.types import Definition, expand_opt_references # noqa
from kitty.constants import str_version, website_url # noqa from kitty.constants import str_version, website_url # noqa
# config {{{ # config {{{
@ -217,7 +217,8 @@ if you specify a program-to-run you can use the special placeholder
from kitty.remote_control import cli_msg, global_options_spec from kitty.remote_control import cli_msg, global_options_spec
with open('generated/cli-kitty-at.rst', 'w') as f: with open('generated/cli-kitty-at.rst', 'w') as f:
p = partial(print, file=f) p = partial(print, file=f)
p('kitty @\n' + '-' * 80) p('kitty @')
p('-' * 80)
p('.. program::', 'kitty @') p('.. program::', 'kitty @')
p('\n\n' + as_rst( p('\n\n' + as_rst(
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @')) global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
@ -225,7 +226,8 @@ if you specify a program-to-run you can use the special placeholder
for cmd_name in sorted(all_command_names()): for cmd_name in sorted(all_command_names()):
func = command_for_name(cmd_name) func = command_for_name(cmd_name)
p(f'.. _at_{func.name}:\n') p(f'.. _at_{func.name}:\n')
p('kitty @', func.name + '\n' + '-' * 120) p('kitty @', func.name)
p('-' * 120)
p('.. program::', 'kitty @', func.name) p('.. program::', 'kitty @', func.name)
p('\n\n' + as_rst(*cli_params_for(func))) p('\n\n' + as_rst(*cli_params_for(func)))
from kittens.runner import get_kitten_cli_docs from kittens.runner import get_kitten_cli_docs
@ -234,12 +236,12 @@ if you specify a program-to-run you can use the special placeholder
if data: if data:
with open(f'generated/cli-kitten-{kitten}.rst', 'w') as f: with open(f'generated/cli-kitten-{kitten}.rst', 'w') as f:
p = partial(print, file=f) p = partial(print, file=f)
p('.. program::', f'kitty +kitten {kitten}') p('.. program::', 'kitty +kitten', kitten)
p(f'\nSource code for {kitten}') p('\nSource code for', kitten)
p('-' * 72) p('-' * 72)
p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.') p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.')
p('\nCommand Line Interface') p('\nCommand Line Interface')
p('-' * 72, file=f) p('-' * 72)
p('\n\n' + option_spec_as_rst( p('\n\n' + option_spec_as_rst(
data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}', data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}',
heading_char='^')) heading_char='^'))
@ -364,19 +366,6 @@ def link_role(
return [node], [] return [node], []
def expand_opt_references(conf_name: str, text: str) -> str:
conf_name += '.'
def expand(m: Match[str]) -> str:
ref = m.group(1)
if '<' not in ref and '.' not in ref:
full_ref = conf_name + ref
return f':opt:`{ref} <{full_ref}>`'
return str(m.group())
return re.sub(r':opt:`(.+?)`', expand, text)
opt_aliases: Dict[str, str] = {} opt_aliases: Dict[str, str] = {}
shortcut_slugs: Dict[str, Tuple[str, str]] = {} shortcut_slugs: Dict[str, Tuple[str, str]] = {}
@ -414,7 +403,7 @@ def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: st
conf_name, opt = target.partition('.')[::2] conf_name, opt = target.partition('.')[::2]
if not opt: if not opt:
conf_name, opt = 'kitty', conf_name conf_name, opt = 'kitty', conf_name
full_name = conf_name + '.' + opt full_name = f'{conf_name}.{opt}'
return title, opt_aliases.get(full_name, full_name) return title, opt_aliases.get(full_name, full_name)
@ -422,7 +411,7 @@ def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, titl
conf_name, slug = target.partition('.')[::2] conf_name, slug = target.partition('.')[::2]
if not slug: if not slug:
conf_name, slug = 'kitty', conf_name conf_name, slug = 'kitty', conf_name
full_name = conf_name + '.' + slug full_name = f'{conf_name}.{slug}'
try: try:
target, stitle = shortcut_slugs[full_name] target, stitle = shortcut_slugs[full_name]
except KeyError: except KeyError:

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import subprocess import subprocess
from collections import defaultdict from collections import defaultdict
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
@ -42,7 +43,7 @@ def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str)
lines = [] lines = []
for ch in type_map['flag']: for ch in type_map['flag']:
attr, allowed_values = keymap[ch] attr, allowed_values = keymap[ch]
q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values) q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values))
lines.append(f''' lines.append(f'''
case {attr}: {{ case {attr}: {{
g.{attr} = screen->parser_buf[pos++] & 0xff; g.{attr} = screen->parser_buf[pos++] & 0xff;
@ -67,7 +68,7 @@ def cmd_for_report(report_name: str, keymap: KeymapType, type_map: Dict[str, Any
flag_fmt, flag_attrs = [], [] flag_fmt, flag_attrs = [], []
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype] cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
for ch in type_map[atype]: for ch in type_map[atype]:
flag_fmt.append('s' + cv) flag_fmt.append(f's{cv}')
attr = keymap[ch][0] attr = keymap[ch][0]
flag_attrs.append(f'"{attr}", {conv}g.{attr}') flag_attrs.append(f'"{attr}", {conv}g.{attr}')
return ' '.join(flag_fmt), ', '.join(flag_attrs) return ' '.join(flag_fmt), ', '.join(flag_attrs)
@ -240,7 +241,7 @@ static inline void
def write_header(text: str, path: str) -> None: def write_header(text: str, path: str) -> None:
with open(path, 'w') as f: with open(path, 'w') as f:
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n') print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n')
print('#pragma once', file=f) print('#pragma once', file=f)
print(text, file=f) print(text, file=f)
subprocess.check_call(['clang-format', '-i', path]) subprocess.check_call(['clang-format', '-i', path])

View File

@ -238,7 +238,7 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
end = raw.index(end_q) end = raw.index(end_q)
except ValueError: except ValueError:
raise SystemExit(f'Failed to find "{end_q}" in {path}') raise SystemExit(f'Failed to find "{end_q}" in {path}')
raw = raw[:start] + start_q + '\n' + text + '\n' + raw[end:] raw = f'{raw[:start]}{start_q}\n{text}\n{raw[end:]}'
f.seek(0) f.seek(0)
f.truncate(0) f.truncate(0)
f.write(raw) f.write(raw)
@ -368,7 +368,7 @@ def generate_ctrl_mapping() -> None:
val = str(ctrl_mapping[k]) val = str(ctrl_mapping[k])
items.append(val) items.append(val)
if k in "\\'": if k in "\\'":
k = '\\' + k k = f'\\{k}'
mi.append(f" case '{k}': return {val};") mi.append(f" case '{k}': return {val};")
for line_items in chunks(items, 6): for line_items in chunks(items, 6):

View File

@ -80,7 +80,7 @@ def init_env(
) -> Env: ) -> Env:
ans = env.copy() ans = env.copy()
ans.cflags.append('-fPIC') ans.cflags.append('-fPIC')
ans.cppflags.append('-D_GLFW_' + module.upper()) ans.cppflags.append(f'-D_GLFW_{module.upper()}')
ans.cppflags.append('-D_GLFW_BUILD_DLL') ans.cppflags.append('-D_GLFW_BUILD_DLL')
with open(os.path.join(base, 'source-info.json')) as f: with open(os.path.join(base, 'source-info.json')) as f:

View File

@ -101,7 +101,7 @@ def remote_hostname(path: str) -> Tuple[Optional[str], Optional[str]]:
def resolve_remote_name(path: str, default: str) -> str: def resolve_remote_name(path: str, default: str) -> str:
remote_dir, rh = remote_hostname(path) remote_dir, rh = remote_hostname(path)
if remote_dir and rh: if remote_dir and rh:
return rh + ':' + os.path.relpath(path, remote_dir) return f'{rh}:{os.path.relpath(path, remote_dir)}'
return default return default

View File

@ -97,7 +97,7 @@ def highlight_data(code: str, filename: str, aliases: Optional[Dict[str, str]] =
base, ext = os.path.splitext(filename) base, ext = os.path.splitext(filename)
alias = aliases.get(ext[1:]) alias = aliases.get(ext[1:])
if alias is not None: if alias is not None:
filename = base + '.' + alias filename = f'{base}.{alias}'
try: try:
lexer = get_lexer_for_filename(filename, stripnl=False) lexer = get_lexer_for_filename(filename, stripnl=False)
except ClassNotFound: except ClassNotFound:

View File

@ -347,7 +347,7 @@ class DiffHandler(Handler):
text = line.text text = line.text
if line.image_data is not None: if line.image_data is not None:
image_involved = True image_involved = True
self.write('\r\x1b[K' + text + '\x1b[0m') self.write(f'\r\x1b[K{text}\x1b[0m')
if self.current_search is not None: if self.current_search is not None:
self.current_search.highlight_line(self.write, lpos) self.current_search.highlight_line(self.write, lpos)
if i < num - 1: if i < num - 1:
@ -465,7 +465,7 @@ class DiffHandler(Handler):
) )
else: else:
counts = styled(f'{len(self.current_search)} matches', fg=self.opts.margin_fg) counts = styled(f'{len(self.current_search)} matches', fg=self.opts.margin_fg)
suffix = counts + ' ' + scroll_frac suffix = f'{counts} {scroll_frac}'
prefix = styled(':', fg=self.opts.margin_fg) prefix = styled(':', fg=self.opts.margin_fg)
filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix) filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
text = '{}{}{}'.format(prefix, ' ' * filler, suffix) text = '{}{}{}'.format(prefix, ' ' * filler, suffix)

View File

@ -244,14 +244,14 @@ class Differ:
except Exception as e: except Exception as e:
return f'Running git diff for {left_path} vs. {right_path} generated an exception: {e}' return f'Running git diff for {left_path} vs. {right_path} generated an exception: {e}'
if not ok: if not ok:
return output + f'\nRunning git diff for {left_path} vs. {right_path} failed' return f'{output}\nRunning git diff for {left_path} vs. {right_path} failed'
left_lines = lines_for_path(left_path) left_lines = lines_for_path(left_path)
right_lines = lines_for_path(right_path) right_lines = lines_for_path(right_path)
try: try:
patch = parse_patch(output) patch = parse_patch(output)
except Exception: except Exception:
import traceback import traceback
return traceback.format_exc() + f'\nParsing diff for {left_path} vs. {right_path} failed' return f'{traceback.format_exc()}\nParsing diff for {left_path} vs. {right_path} failed'
else: else:
ans[key] = patch ans[key] = patch
return ans return ans

View File

@ -101,7 +101,7 @@ def human_readable(size: int, sep: str = ' ') -> str:
s = s[:s.find(".")+2] s = s[:s.find(".")+2]
if s.endswith('.0'): if s.endswith('.0'):
s = s[:-2] s = s[:-2]
return s + sep + suffix return f'{s}{sep}{suffix}'
def fit_in(text: str, count: int) -> str: def fit_in(text: str, count: int) -> str:
@ -110,7 +110,7 @@ def fit_in(text: str, count: int) -> str:
return text return text
if count > 1: if count > 1:
p = truncate_point_for_length(text, count - 1) p = truncate_point_for_length(text, count - 1)
return text[:p] + '' return f'{text[:p]}'
def fill_in(text: str, sz: int) -> str: def fill_in(text: str, sz: int) -> str:
@ -127,8 +127,8 @@ def place_in(text: str, sz: int) -> str:
def format_func(which: str) -> Callable[[str], str]: def format_func(which: str) -> Callable[[str], str]:
def formatted(text: str) -> str: def formatted(text: str) -> str:
fmt = formats[which] fmt = formats[which]
return '\x1b[' + fmt + 'm' + text + '\x1b[0m' return f'\x1b[{fmt}m{text}\x1b[0m'
formatted.__name__ = which + '_format' formatted.__name__ = f'{which}_format'
return formatted return formatted
@ -148,8 +148,8 @@ highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_high
def highlight_boundaries(ltype: str) -> Tuple[str, str]: def highlight_boundaries(ltype: str) -> Tuple[str, str]:
s, e = highlight_map[ltype] s, e = highlight_map[ltype]
start = '\x1b[' + formats[s] + 'm' start = f'\x1b[{formats[s]}m'
stop = '\x1b[' + formats[e] + 'm' stop = f'\x1b[{formats[e]}m'
return start, stop return start, stop

View File

@ -729,7 +729,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
else: else:
import shlex import shlex
text = ' '.join(shlex.quote(arg) for arg in cmd) text = ' '.join(shlex.quote(arg) for arg in cmd)
w.paste_bytes(text + '\r') w.paste_bytes(f'{text}\r')
elif action == 'background': elif action == 'background':
import subprocess import subprocess
subprocess.Popen(cmd, cwd=data['cwd']) subprocess.Popen(cmd, cwd=data['cwd'])

View File

@ -245,7 +245,7 @@ def main(args: List[str] = sys.argv) -> None:
raise SystemExit(f'Unknown queries: {", ".join(extra)}') raise SystemExit(f'Unknown queries: {", ".join(extra)}')
for key, val in do_queries(queries, cli_opts).items(): for key, val in do_queries(queries, cli_opts).items():
print(key + ':', val) print(f'{key}:', val)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -52,7 +52,7 @@ class KeysHandler(Handler):
self.cmd.colored(etype + ' ', 'yellow') self.cmd.colored(etype + ' ', 'yellow')
self.cmd.styled(key_event.text, italic=True) self.cmd.styled(key_event.text, italic=True)
self.print() self.print()
rep = 'CSI ' + encode_key_event(key_event)[2:] rep = f'CSI {encode_key_event(key_event)[2:]}'
rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1] rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1]
self.cmd.styled(rep, fg='magenta') self.cmd.styled(rep, fg='magenta')
if (key_event.shifted_key or key_event.alternate_key): if (key_event.shifted_key or key_event.alternate_key):

View File

@ -17,7 +17,7 @@ def print_key(raw: bytearray) -> None:
unix = '' unix = ''
for ch in raw: for ch in raw:
if ch < len(ctrl_keys): if ch < len(ctrl_keys):
unix += '^' + ctrl_keys[ch] unix += f'^{ctrl_keys[ch]}'
elif ch == 127: elif ch == 127:
unix += '^?' unix += '^?'
else: else:

View File

@ -281,7 +281,7 @@ def complete_choices(ans: Completions, prefix: str, title: str, choices: Iterabl
if q.startswith(effective_prefix): if q.startswith(effective_prefix):
if comma_separated: if comma_separated:
tq = q tq = q
q = hidden_prefix + q + ',' q = f'{hidden_prefix}{q},'
word_transforms[q] = tq word_transforms[q] = tq
matches[q] = '' matches[q] = ''
ans.add_match_group(title, matches, trailing_space=not comma_separated, word_transforms=word_transforms) ans.add_match_group(title, matches, trailing_space=not comma_separated, word_transforms=word_transforms)

View File

@ -131,7 +131,7 @@ def get_ssh_cli() -> Tuple[Set[str], Set[str]]:
other_ssh_args: Set[str] = set() other_ssh_args: Set[str] = set()
boolean_ssh_args: Set[str] = set() boolean_ssh_args: Set[str] = set()
for k, v in ssh_options().items(): for k, v in ssh_options().items():
k = '-' + k k = f'-{k}'
if v: if v:
other_ssh_args.add(k) other_ssh_args.add(k)
else: else:
@ -213,7 +213,7 @@ class InvalidSSHArgs(ValueError):
def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]: def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
boolean_ssh_args, other_ssh_args = get_ssh_cli() boolean_ssh_args, other_ssh_args = get_ssh_cli()
passthrough_args = {'-' + x for x in 'Nnf'} passthrough_args = {f'-{x}' for x in 'Nnf'}
ssh_args = [] ssh_args = []
server_args: List[str] = [] server_args: List[str] = []
expecting_option_val = False expecting_option_val = False
@ -230,7 +230,7 @@ def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
# could be a multi-character option # could be a multi-character option
all_args = argument[1:] all_args = argument[1:]
for i, arg in enumerate(all_args): for i, arg in enumerate(all_args):
arg = '-' + arg arg = f'-{arg}'
if arg in passthrough_args: if arg in passthrough_args:
passthrough = True passthrough = True
if arg in boolean_ssh_args: if arg in boolean_ssh_args:

View File

@ -376,7 +376,7 @@ def fetch_themes(
needs_delete = False needs_delete = False
try: try:
with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(dest_path), dir=os.path.dirname(dest_path), delete=False) as f: with tempfile.NamedTemporaryFile(suffix=f'-{os.path.basename(dest_path)}', dir=os.path.dirname(dest_path), delete=False) as f:
needs_delete = True needs_delete = True
shutil.copyfileobj(res, f) shutil.copyfileobj(res, f)
f.flush() f.flush()
@ -405,7 +405,7 @@ def theme_name_from_file_name(fname: str) -> str:
ans = ans.replace('_', ' ') ans = ans.replace('_', ' ')
def camel_case(m: 'Match[str]') -> str: def camel_case(m: 'Match[str]') -> str:
return str(m.group(1) + ' ' + m.group(2)) return f'{m.group(1)} {m.group(2)}'
ans = re.sub(r'([a-z])([A-Z])', camel_case, ans) ans = re.sub(r'([a-z])([A-Z])', camel_case, ans)
return ' '.join(x.capitalize() for x in filter(None, ans.split())) return ' '.join(x.capitalize() for x in filter(None, ans.split()))
@ -533,7 +533,7 @@ class Theme:
raw = '' raw = ''
nraw = patch_conf(raw, self.name) nraw = patch_conf(raw, self.name)
if raw: if raw:
with open(confpath + '.bak', 'w') as f: with open(f'{confpath}.bak', 'w') as f:
f.write(raw) f.write(raw)
atomic_save(nraw.encode('utf-8'), confpath) atomic_save(nraw.encode('utf-8'), confpath)
if reload_in == 'parent': if reload_in == 'parent':

View File

@ -37,7 +37,7 @@ def limit_length(text: str, limit: int = 32) -> str:
x = truncate_point_for_length(text, limit - 1) x = truncate_point_for_length(text, limit - 1)
if x >= len(text): if x >= len(text):
return text return text
return text[:x] + '' return f'{text[:x]}'
class State(Enum): class State(Enum):
@ -332,7 +332,7 @@ class ThemesHandler(Handler):
for line, width, is_current in self.themes_list.lines(num_rows): for line, width, is_current in self.themes_list.lines(num_rows):
num_rows -= 1 num_rows -= 1
if is_current: if is_current:
line = line.replace(MARK_AFTER, '\033[' + color_code('green') + 'm') line = line.replace(MARK_AFTER, f'\033[{color_code("green")}m')
self.cmd.styled('>' if is_current else ' ', fg='green') self.cmd.styled('>' if is_current else ' ', fg='green')
self.cmd.styled(line, bold=is_current, fg='green' if is_current else None) self.cmd.styled(line, bold=is_current, fg='green' if is_current else None)
self.cmd.move_cursor_by(mw - width, 'right') self.cmd.move_cursor_by(mw - width, 'right')

View File

@ -155,12 +155,12 @@ def develop() -> None:
import sys import sys
src = sys.argv[-1] src = sys.argv[-1]
sig_loader = LoadSignature() sig_loader = LoadSignature()
with open(src + '.sig', 'wb') as f: with open(f'{src}.sig', 'wb') as f:
for chunk in signature_of_file(src): for chunk in signature_of_file(src):
sig_loader.add_chunk(chunk) sig_loader.add_chunk(chunk)
f.write(chunk) f.write(chunk)
sig_loader.commit() sig_loader.commit()
with open(src + '.delta', 'wb') as f, PatchFile(src, src + '.output') as patcher: with open(f'{src}.delta', 'wb') as f, PatchFile(src, f'{src}.output') as patcher:
for chunk in delta_for_file(src, sig_loader.signature): for chunk in delta_for_file(src, sig_loader.signature):
f.write(chunk) f.write(chunk)
patcher.write(chunk) patcher.write(chunk)

View File

@ -45,7 +45,7 @@ def render_path_in_width(path: str, width: int) -> str:
if wcswidth(path) <= width: if wcswidth(path) <= width:
return path return path
x = truncate_point_for_length(path, width - 1) x = truncate_point_for_length(path, width - 1)
return path[:x] + '' return f'{path[:x]}'
def render_seconds(val: float) -> str: def render_seconds(val: float) -> str:

View File

@ -331,7 +331,7 @@ class Dircolors:
# change .xyz to *.xyz # change .xyz to *.xyz
yield '*' + pair[0], pair[1] yield '*' + pair[0], pair[1]
return ':'.join('%s=%s' % pair for pair in gen_pairs()) return ':'.join('{}={}'.format(*pair) for pair in gen_pairs())
def _format_code(self, text: str, code: str) -> str: def _format_code(self, text: str, code: str) -> str:
val = self.codes.get(code) val = self.codes.get(code)

View File

@ -163,7 +163,7 @@ def set_scrolling_region(screen_size: Optional['ScreenSize'] = None, top: Option
@cmd @cmd
def scroll_screen(amt: int = 1) -> str: def scroll_screen(amt: int = 1) -> str:
return '\033[' + str(abs(amt)) + ('T' if amt < 0 else 'S') return f'\033[{abs(amt)}{"T" if amt < 0 else "S"}'
STANDARD_COLORS = {'black': 0, 'red': 1, 'green': 2, 'yellow': 3, 'blue': 4, 'magenta': 5, 'cyan': 6, 'gray': 7, 'white': 7} STANDARD_COLORS = {'black': 0, 'red': 1, 'green': 2, 'yellow': 3, 'blue': 4, 'magenta': 5, 'cyan': 6, 'gray': 7, 'white': 7}
@ -465,7 +465,7 @@ def as_type_stub() -> str:
for name, func in all_cmds.items(): for name, func in all_cmds.items():
args = ', '.join(func_sig(func)) args = ', '.join(func_sig(func))
if args: if args:
args = ', ' + args args = f', {args}'
methods.append(f' def {name}(self{args}) -> str: pass') methods.append(f' def {name}(self{args}) -> str: pass')
ans += ['', '', 'class CMD:'] + methods ans += ['', '', 'class CMD:'] + methods

View File

@ -192,7 +192,7 @@ class Table:
if w < 2: if w < 2:
text += ' ' * (2 - w) text += ' ' * (2 - w)
if len(desc) > space_for_desc: if len(desc) > space_for_desc:
text += desc[:space_for_desc - 1] + '' text += f'{desc[:space_for_desc - 1]}'
else: else:
text += desc text += desc
extra = space_for_desc - len(desc) extra = space_for_desc - len(desc)

View File

@ -62,7 +62,7 @@ else:
except Exception: except Exception:
continue continue
try: try:
with open('/proc/' + x + '/stat', 'rb') as f: with open(f'/proc/{x}/stat', 'rb') as f:
raw = f.read().decode('utf-8') raw = f.read().decode('utf-8')
except OSError: except OSError:
continue continue
@ -268,7 +268,7 @@ class Child:
# https://github.com/kovidgoyal/kitty/issues/1870 # https://github.com/kovidgoyal/kitty/issues/1870
# xterm, urxvt, konsole and gnome-terminal do not do it in my # xterm, urxvt, konsole and gnome-terminal do not do it in my
# testing. # testing.
argv[0] = ('-' + exe.split('/')[-1]) argv[0] = (f'-{exe.split("/")[-1]}')
exe = which(exe) or exe exe = which(exe) or exe
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd) pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd)
os.close(slave) os.close(slave)

View File

@ -82,7 +82,7 @@ def generate_stub() -> None:
for cmd_name in all_command_names(): for cmd_name in all_command_names():
cmd = command_for_name(cmd_name) cmd = command_for_name(cmd_name)
if cmd.options_spec: if cmd.options_spec:
do(cmd.options_spec, cmd.__class__.__name__ + 'RCOptions') do(cmd.options_spec, f'{cmd.__class__.__name__}RCOptions')
save_type_stub(text, __file__) save_type_stub(text, __file__)

View File

@ -12,8 +12,8 @@ from contextlib import suppress
from typing import Any from typing import Any
CSI = '\033[' CSI = '\x1b['
OSC = '\033]' OSC = '\x1b]'
def write(x: str) -> None: def write(x: str) -> None:
@ -42,11 +42,11 @@ def screen_alternate_keypad_mode() -> None:
def screen_cursor_position(y: int, x: int) -> None: def screen_cursor_position(y: int, x: int) -> None:
write(CSI + f'{y};{x}H') write(f'{CSI}{y};{x}H')
def screen_cursor_forward(amt: int) -> None: def screen_cursor_forward(amt: int) -> None:
write(CSI + '%sC' % amt) write(f'{CSI}{amt}C')
def screen_save_cursor() -> None: def screen_save_cursor() -> None:
@ -58,105 +58,105 @@ def screen_restore_cursor() -> None:
def screen_cursor_back1(amt: int) -> None: def screen_cursor_back1(amt: int) -> None:
write(CSI + '%sD' % amt) write(f'{CSI}{amt}D')
def screen_save_modes() -> None: def screen_save_modes() -> None:
write(CSI + '?s') write(f'{CSI}?s')
def screen_restore_modes() -> None: def screen_restore_modes() -> None:
write(CSI + '?r') write(f'{CSI}?r')
def screen_designate_charset(which: int, to: int) -> None: def screen_designate_charset(which: int, to: int) -> None:
w = '()'[int(which)] w = '()'[int(which)]
t = chr(int(to)) t = chr(int(to))
write('\033' + w + t) write(f'\x1b{w}{t}')
def select_graphic_rendition(*a: int) -> None: def select_graphic_rendition(*a: int) -> None:
write(CSI + '%sm' % ';'.join(map(str, a))) write(f'{CSI}{";".join(map(str, a))}m')
def screen_cursor_to_column(c: int) -> None: def screen_cursor_to_column(c: int) -> None:
write(CSI + '%dG' % c) write(f'{CSI}{c}G')
def screen_cursor_to_line(ln: int) -> None: def screen_cursor_to_line(ln: int) -> None:
write(CSI + '%dd' % ln) write(f'{CSI}{ln}d')
def screen_set_mode(x: int, private: bool) -> None: def screen_set_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 'h') write(f'{CSI}{"?" if private else ""}{x}h')
def screen_save_mode(x: int, private: bool) -> None: def screen_save_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 's') write(f'{CSI}{"?" if private else ""}{x}s')
def screen_reset_mode(x: int, private: bool) -> None: def screen_reset_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 'l') write(f'{CSI}{"?" if private else ""}{x}l')
def screen_restore_mode(x: int, private: bool) -> None: def screen_restore_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 'r') write(f'{CSI}{"?" if private else ""}{x}r')
def screen_set_margins(t: int, b: int) -> None: def screen_set_margins(t: int, b: int) -> None:
write(CSI + '%d;%dr' % (t, b)) write(f'{CSI}{t};{b}r')
def screen_indexn(n: int) -> None: def screen_indexn(n: int) -> None:
write(CSI + '%dS' % n) write(f'{CSI}{n}S')
def screen_delete_characters(count: int) -> None: def screen_delete_characters(count: int) -> None:
write(CSI + '%dP' % count) write(f'{CSI}{count}P')
def screen_push_colors(which: int) -> None: def screen_push_colors(which: int) -> None:
write(CSI + '%d#P' % which) write(f'{CSI}{which}#P')
def screen_pop_colors(which: int) -> None: def screen_pop_colors(which: int) -> None:
write(CSI + '%d#Q' % which) write(f'{CSI}{which}#Q')
def screen_report_colors() -> None: def screen_report_colors() -> None:
write(CSI + '#R') write(f'{CSI}#R')
def screen_repeat_character(num: int) -> None: def screen_repeat_character(num: int) -> None:
write(CSI + '%db' % num) write(f'{CSI}{num}b')
def screen_insert_characters(count: int) -> None: def screen_insert_characters(count: int) -> None:
write(CSI + '%d@' % count) write(f'{CSI}{count}@')
def screen_scroll(count: int) -> None: def screen_scroll(count: int) -> None:
write(CSI + '%dS' % count) write(f'{CSI}{count}S')
def screen_erase_in_display(how: int, private: bool) -> None: def screen_erase_in_display(how: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(how) + 'J') write(f'{CSI}{"?" if private else ""}{how}J')
def screen_erase_in_line(how: int, private: bool) -> None: def screen_erase_in_line(how: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(how) + 'K') write(f'{CSI}{"?" if private else ""}{how}K')
def screen_delete_lines(num: int) -> None: def screen_delete_lines(num: int) -> None:
write(CSI + str(num) + 'M') write(f'{CSI}{num}M')
def screen_cursor_up2(count: int) -> None: def screen_cursor_up2(count: int) -> None:
write(CSI + '%dA' % count) write(f'{CSI}{count}A')
def screen_cursor_down(count: int) -> None: def screen_cursor_down(count: int) -> None:
write(CSI + '%dB' % count) write(f'{CSI}{count}B')
def screen_carriage_return() -> None: def screen_carriage_return() -> None:
@ -176,11 +176,11 @@ def screen_backspace() -> None:
def screen_set_cursor(mode: int, secondary: int) -> None: def screen_set_cursor(mode: int, secondary: int) -> None:
write(CSI + '%d q' % secondary) write(f'{CSI}{secondary} q')
def screen_insert_lines(num: int) -> None: def screen_insert_lines(num: int) -> None:
write(CSI + '%dL' % num) write(f'{CSI}{num}L')
def draw(*a: str) -> None: def draw(*a: str) -> None:
@ -188,7 +188,7 @@ def draw(*a: str) -> None:
def screen_manipulate_title_stack(op: int, which: int) -> None: def screen_manipulate_title_stack(op: int, which: int) -> None:
write(CSI + '%d;%dt' % (op, which)) write(f'{CSI}{op};{which}t')
def report_device_attributes(mode: int, char: int) -> None: def report_device_attributes(mode: int, char: int) -> None:
@ -197,21 +197,22 @@ def report_device_attributes(mode: int, char: int) -> None:
x += chr(char) x += chr(char)
if mode: if mode:
x += str(mode) x += str(mode)
write(CSI + x + 'c') write(f'{CSI}{x}c')
def screen_decsace(mode: int) -> None: def screen_decsace(mode: int) -> None:
write(CSI + str(mode) + '*x') write(f'{CSI}{mode}*x')
def screen_set_8bit_controls(mode: int) -> None: def screen_set_8bit_controls(mode: int) -> None:
write('\x1b ' + ('G' if mode else 'F')) write(f'\x1b {"G" if mode else "F"}')
def write_osc(code: int, string: str = '') -> None: def write_osc(code: int, string: str = '') -> None:
if string: if string:
string = ';' + string write(f'{OSC}{code};{string}\x07')
write(OSC + str(code) + string + '\x07') else:
write(f'{OSC}{code}\x07')
set_dynamic_color = set_color_table_color = process_cwd_notification = write_osc set_dynamic_color = set_color_table_color = process_cwd_notification = write_osc
@ -235,7 +236,7 @@ def clipboard_control(payload: str) -> None:
clipboard_control_pending += data.lstrip(';') clipboard_control_pending += data.lstrip(';')
payload = clipboard_control_pending payload = clipboard_control_pending
clipboard_control_pending = '' clipboard_control_pending = ''
write(OSC + payload + '\x07') write(f'{OSC}{payload}\x07')
def replay(raw: str) -> None: def replay(raw: str) -> None:

View File

@ -270,7 +270,7 @@ def zsh_output_serializer(ans: Completions) -> str:
yield ans yield ans
for description, matches in ans.match_groups.items(): for description, matches in ans.match_groups.items():
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote('%B' + description + '%b')] cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(f'%B{description}%b')]
if not matches.trailing_space: if not matches.trailing_space:
cmd += ['-S', '""'] cmd += ['-S', '""']
if matches.is_files: if matches.is_files:
@ -350,10 +350,10 @@ def fish2_output_serializer(ans: Completions) -> str:
def completions_for_first_word(ans: Completions, prefix: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> None: def completions_for_first_word(ans: Completions, prefix: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> None:
cmds = ['@' + c for c in remote_control_command_names()] cmds = [f'@{c}' for c in remote_control_command_names()]
ans.add_match_group('Entry points', { ans.add_match_group('Entry points', {
k: '' for k in k: '' for k in
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points] list(entry_points) + cmds + [f'+{k}' for k in namespaced_entry_points]
if not prefix or k.startswith(prefix) if not prefix or k.startswith(prefix)
}) })
if prefix: if prefix:
@ -443,7 +443,7 @@ def complete_alias_map(
long_opt = option_map.get(parts[0]) long_opt = option_map.get(parts[0])
if long_opt is not None and complete_args is not None: if long_opt is not None and complete_args is not None:
complete_args(ans, long_opt, parts[1], Delegate()) complete_args(ans, long_opt, parts[1], Delegate())
ans.add_prefix(parts[0] + '=') ans.add_prefix(f'{parts[0]}=')
return return
opt = option_map.get(w) opt = option_map.get(w)
if w is last_word and not new_word: if w is last_word and not new_word:
@ -682,7 +682,7 @@ def find_completions(words: Sequence[str], new_word: bool, entry_points: Iterabl
if words[0].startswith('@'): if words[0].startswith('@'):
if len(words) == 1 and not new_word: if len(words) == 1 and not new_word:
prefix = words[0] prefix = words[0]
ans.add_match_group('Remote control commands', {'@' + c: '' for c in remote_control_command_names() if c.startswith(prefix)}) ans.add_match_group('Remote control commands', {f'@{c}': '' for c in remote_control_command_names() if c.startswith(prefix)})
else: else:
complete_remote_command(ans, words[0][1:], words[1:], new_word) complete_remote_command(ans, words[0][1:], words[1:], new_word)
if words[0] == '+': if words[0] == '+':

View File

@ -418,9 +418,9 @@ def generate_c_conversion(loc: str, ctypes: List[Option]) -> str:
def write_output(loc: str, defn: Definition) -> None: def write_output(loc: str, defn: Definition) -> None:
cls, tc = generate_class(defn, loc) cls, tc = generate_class(defn, loc)
with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f: with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f:
f.write(cls + '\n') f.write(f'{cls}\n')
with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f: with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f:
f.write(tc + '\n') f.write(f'{tc}\n')
ctypes = [] ctypes = []
for opt in defn.root_group.iter_all_non_groups(): for opt in defn.root_group.iter_all_non_groups():
if isinstance(opt, Option) and opt.ctype: if isinstance(opt, Option) and opt.ctype:
@ -428,7 +428,7 @@ def write_output(loc: str, defn: Definition) -> None:
if ctypes: if ctypes:
c = generate_c_conversion(loc, ctypes) c = generate_c_conversion(loc, ctypes)
with open(os.path.join(*loc.split('.'), 'options', 'to-c-generated.h'), 'w') as f: with open(os.path.join(*loc.split('.'), 'options', 'to-c-generated.h'), 'w') as f:
f.write(c + '\n') f.write(f'{c}\n')
def main() -> None: def main() -> None:
@ -454,6 +454,6 @@ def main() -> None:
loc = package_name loc = package_name
cls, tc = generate_class(defn, loc) cls, tc = generate_class(defn, loc)
with open(os.path.join(os.path.dirname(path), 'kitten_options_types.py'), 'w') as f: with open(os.path.join(os.path.dirname(path), 'kitten_options_types.py'), 'w') as f:
f.write(cls + '\n') f.write(f'{cls}\n')
with open(os.path.join(os.path.dirname(path), 'kitten_options_parse.py'), 'w') as f: with open(os.path.join(os.path.dirname(path), 'kitten_options_parse.py'), 'w') as f:
f.write(tc + '\n') f.write(f'{tc}\n')

View File

@ -37,8 +37,8 @@ def expand_opt_references(conf_name: str, text: str) -> str:
def expand(m: 'Match[str]') -> str: def expand(m: 'Match[str]') -> str:
ref = m.group(1) ref = m.group(1)
if '<' not in ref and '.' not in ref: if '<' not in ref and '.' not in ref:
full_ref = conf_name + ref # full ref
return f':opt:`{ref} <{full_ref}>`' return f':opt:`{ref} <{conf_name}{ref}>`'
return str(m.group()) return str(m.group())
return re.sub(r':opt:`(.+?)`', expand, text) return re.sub(r':opt:`(.+?)`', expand, text)
@ -214,7 +214,7 @@ class Option:
if not self.documented: if not self.documented:
return ans return ans
mopts = [self] + option_group mopts = [self] + option_group
a('.. opt:: ' + ', '.join(conf_name + '.' + mo.name for mo in mopts)) a('.. opt:: ' + ', '.join(f'{conf_name}.{mo.name}' for mo in mopts))
a('.. code-block:: conf') a('.. code-block:: conf')
a('') a('')
sz = max(len(x.name) for x in mopts) sz = max(len(x.name) for x in mopts)
@ -330,7 +330,7 @@ class Mapping:
raise ValueError(f'The shortcut for {self.name} has no short_text') raise ValueError(f'The shortcut for {self.name} has no short_text')
sc_text = f'{conf_name}.{self.short_text}' sc_text = f'{conf_name}.{self.short_text}'
shortcut_slugs[f'{conf_name}.{self.name}'] = (sc_text, self.key_text.replace('kitty_mod', kitty_mod)) shortcut_slugs[f'{conf_name}.{self.name}'] = (sc_text, self.key_text.replace('kitty_mod', kitty_mod))
a('.. shortcut:: ' + sc_text) a(f'.. shortcut:: {sc_text}')
block_started = False block_started = False
for sc in [self] + action_group: for sc in [self] + action_group:
if sc.add_to_default and sc.documented: if sc.add_to_default and sc.documented:
@ -534,7 +534,7 @@ class Group:
ans[i] = ' '.join(parts) ans[i] = ' '.join(parts)
if commented: if commented:
ans = [x if x.startswith('#') or not x.strip() else ('# ' + x) for x in ans] ans = [x if x.startswith('#') or not x.strip() else (f'# {x}') for x in ans]
return ans return ans

View File

@ -54,7 +54,7 @@ def atomic_save(data: bytes, path: str) -> None:
@contextmanager @contextmanager
def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]: def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
cached_path = os.path.join(cache_dir(), name + '.json') cached_path = os.path.join(cache_dir(), f'{name}.json')
cached_values: Dict[str, Any] = {} cached_values: Dict[str, Any] = {}
try: try:
with open(cached_path, 'rb') as f: with open(cached_path, 'rb') as f:

View File

@ -29,7 +29,7 @@ def create_font_map(all_fonts: Iterable[CoreTextFont]) -> FontMap:
ps = (x['postscript_name'] or '').lower() ps = (x['postscript_name'] or '').lower()
ans['family_map'].setdefault(f, []).append(x) ans['family_map'].setdefault(f, []).append(x)
ans['ps_map'].setdefault(ps, []).append(x) ans['ps_map'].setdefault(ps, []).append(x)
ans['full_map'].setdefault(f + ' ' + s, []).append(x) ans['full_map'].setdefault(f'{f} {s}', []).append(x)
return ans return ans
@ -45,7 +45,7 @@ def list_fonts() -> Generator[ListedFont, None, None]:
for fd in coretext_all_fonts(): for fd in coretext_all_fonts():
f = fd['family'] f = fd['family']
if f: if f:
fn = (f + ' ' + (fd['style'] or '')).strip() fn = f'{f} {fd.get("style", "")}'.strip()
is_mono = bool(fd['monospace']) is_mono = bool(fd['monospace'])
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono} yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono}

View File

@ -58,7 +58,7 @@ def list_fonts() -> Generator[ListedFont, None, None]:
if fn_: if fn_:
fn = str(fn_) fn = str(fn_)
else: else:
fn = (f + ' ' + str(fd.get('style', ''))).strip() fn = f'{f} {fd.get("style", "")}'.strip()
is_mono = fd.get('spacing') in ('MONO', 'DUAL') is_mono = fd.get('spacing') in ('MONO', 'DUAL')
yield {'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), 'is_monospace': is_mono} yield {'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), 'is_monospace': is_mono}

View File

@ -28,13 +28,13 @@ def main(argv: Sequence[str]) -> None:
groups = create_family_groups() groups = create_family_groups()
for k in sorted(groups, key=lambda x: x.lower()): for k in sorted(groups, key=lambda x: x.lower()):
if isatty: if isatty:
print('\033[1;32m' + k + '\033[m') print(f'\033[1;32m{k}\033[m')
else: else:
print(k) print(k)
for f in sorted(groups[k], key=lambda x: x['full_name'].lower()): for f in sorted(groups[k], key=lambda x: x['full_name'].lower()):
p = f['full_name'] p = f['full_name']
if isatty: if isatty:
p = '\033[3m' + p + '\033[m' p = f'\033[3m{p}\033[m'
if psnames: if psnames:
p += ' ({})'.format(f['postscript_name']) p += ' ({})'.format(f['postscript_name'])
print(' ', p) print(' ', p)

View File

@ -515,7 +515,7 @@ def test_fallback_font(qtext: Optional[str] = None, bold: bool = False, italic:
try: try:
print(text, f) print(text, f)
except UnicodeEncodeError: except UnicodeEncodeError:
sys.stdout.buffer.write((text + ' %s\n' % f).encode('utf-8')) sys.stdout.buffer.write(f'{text} {f}\n'.encode('utf-8'))
def showcase() -> None: def showcase() -> None:

View File

@ -58,7 +58,7 @@ def guess_type(path: str, allow_filesystem_access: bool = False) -> Optional[str
ext = path.rpartition('.')[-1].lower() ext = path.rpartition('.')[-1].lower()
mt = known_extensions.get(ext) mt = known_extensions.get(ext)
if mt in text_mimes: if mt in text_mimes:
mt = 'text/' + mt.split('/', 1)[-1] mt = f'text/{mt.split("/", 1)[-1]}'
if not mt and is_rc_file(path): if not mt and is_rc_file(path):
mt = 'text/plain' mt = 'text/plain'
if not mt and is_folder(path): if not mt and is_folder(path):

View File

@ -219,7 +219,6 @@ encode_function_key(const KeyEvent *ev, char *output) {
case GLFW_FKEY_PAGE_UP: S(5, '~'); case GLFW_FKEY_PAGE_UP: S(5, '~');
case GLFW_FKEY_PAGE_DOWN: S(6, '~'); case GLFW_FKEY_PAGE_DOWN: S(6, '~');
case GLFW_FKEY_HOME: S(1, 'H'); case GLFW_FKEY_HOME: S(1, 'H');
case GLFW_FKEY_KP_BEGIN: S(1, 'E');
case GLFW_FKEY_END: S(1, 'F'); case GLFW_FKEY_END: S(1, 'F');
case GLFW_FKEY_F1: S(1, 'P'); case GLFW_FKEY_F1: S(1, 'P');
case GLFW_FKEY_F2: S(1, 'Q'); case GLFW_FKEY_F2: S(1, 'Q');
@ -233,6 +232,7 @@ encode_function_key(const KeyEvent *ev, char *output) {
case GLFW_FKEY_F10: S(21, '~'); case GLFW_FKEY_F10: S(21, '~');
case GLFW_FKEY_F11: S(23, '~'); case GLFW_FKEY_F11: S(23, '~');
case GLFW_FKEY_F12: S(24, '~'); case GLFW_FKEY_F12: S(24, '~');
case GLFW_FKEY_KP_BEGIN: S(1, 'E');
/* end special numbers */ /* end special numbers */
case GLFW_FKEY_MENU: case GLFW_FKEY_MENU:
// use the same encoding as xterm for this key in legacy mode (F16) // use the same encoding as xterm for this key in legacy mode (F16)

2
kitty/key_encoding.py generated
View File

@ -181,7 +181,7 @@ class EventType(IntEnum):
@lru_cache(maxsize=128) @lru_cache(maxsize=128)
def parse_shortcut(spec: str) -> ParsedShortcut: def parse_shortcut(spec: str) -> ParsedShortcut:
if spec.endswith('+'): if spec.endswith('+'):
spec = spec[:-1] + 'plus' spec = f'{spec[:-1]}plus'
parts = spec.split('+') parts = spec.split('+')
key_name = parts[-1] key_name = parts[-1]
key_name = functional_key_name_aliases.get(key_name.upper(), key_name) key_name = functional_key_name_aliases.get(key_name.upper(), key_name)

View File

@ -63,7 +63,7 @@ else:
import ctypes import ctypes
for suffix in ('.0', ''): for suffix in ('.0', ''):
with suppress(Exception): with suppress(Exception):
lib = ctypes.CDLL('libxkbcommon.so' + suffix) lib = ctypes.CDLL(f'libxkbcommon.so{suffix}')
break break
else: else:
from ctypes.util import find_library from ctypes.util import find_library

View File

@ -369,7 +369,7 @@ def launch(
if opts.stdin_add_formatting: if opts.stdin_add_formatting:
if q in ('@screen', '@screen_scrollback', '@alternate', '@alternate_scrollback', if q in ('@screen', '@screen_scrollback', '@alternate', '@alternate_scrollback',
'@first_cmd_output_on_screen', '@last_cmd_output', '@last_visited_cmd_output'): '@first_cmd_output_on_screen', '@last_cmd_output', '@last_visited_cmd_output'):
q = '@ansi_' + q[1:] q = f'@ansi_{q[1:]}'
if opts.stdin_add_line_wrap_markers: if opts.stdin_add_line_wrap_markers:
q += '_wrap' q += '_wrap'
penv, stdin = boss.process_stdin_source(window=active, stdin=q, copy_pipe_data=pipe_data) penv, stdin = boss.process_stdin_source(window=active, stdin=q, copy_pipe_data=pipe_data)

View File

@ -217,7 +217,7 @@ class Layout:
self.blank_rects: List[Rect] = [] self.blank_rects: List[Rect] = []
self.layout_opts = self.parse_layout_opts(layout_opts) self.layout_opts = self.parse_layout_opts(layout_opts)
assert self.name is not None assert self.name is not None
self.full_name = self.name + ((':' + layout_opts) if layout_opts else '') self.full_name = f'{self.name}:{layout_opts}' if layout_opts else self.name
self.remove_all_biases() self.remove_all_biases()
def bias_increment_for_cell(self, is_horizontal: bool) -> float: def bias_increment_for_cell(self, is_horizontal: bool) -> float:

View File

@ -44,7 +44,7 @@ def set_custom_ibeam_cursor() -> None:
data = f.read() data = f.read()
rgba_data, width, height = load_png_data(data) rgba_data, width, height = load_png_data(data)
c2x = os.path.splitext(beam_cursor_data_file) c2x = os.path.splitext(beam_cursor_data_file)
with open(c2x[0] + '@2x' + c2x[1], 'rb') as f: with open(f'{c2x[0]}@2x{c2x[1]}', 'rb') as f:
data = f.read() data = f.read()
rgba_data2, width2, height2 = load_png_data(data) rgba_data2, width2, height2 = load_png_data(data)
images = (rgba_data, width, height), (rgba_data2, width2, height2) images = (rgba_data, width, height), (rgba_data2, width2, height2)
@ -138,7 +138,7 @@ def get_macos_shortcut_for(
def set_x11_window_icon() -> None: def set_x11_window_icon() -> None:
# max icon size on X11 64bits is 128x128 # max icon size on X11 64bits is 128x128
path, ext = os.path.splitext(logo_png_file) path, ext = os.path.splitext(logo_png_file)
set_default_window_icon(path + '-128' + ext) set_default_window_icon(f'{path}-128{ext}')
def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) -> None: def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) -> None:
@ -220,7 +220,7 @@ def ensure_macos_locale() -> None:
lang = 'en_US' lang = 'en_US'
else: else:
log_error(f'Could not set LANG Cocoa returns language as: {lang}') log_error(f'Could not set LANG Cocoa returns language as: {lang}')
os.environ['LANG'] = lang + '.UTF-8' os.environ['LANG'] = f'{lang}.UTF-8'
@contextmanager @contextmanager

View File

@ -113,7 +113,7 @@ def parse_osc_99(raw: str) -> NotificationCommand:
elif k == 'd': elif k == 'd':
cmd.done = v != '0' cmd.done = v != '0'
elif k == 'a': elif k == 'a':
cmd.actions += ',' + v cmd.actions += f',{v}'
if payload_type not in ('body', 'title'): if payload_type not in ('body', 'title'):
log_error(f'Malformed OSC 99: unknown payload type: {payload_type}') log_error(f'Malformed OSC 99: unknown payload type: {payload_type}')
return NotificationCommand() return NotificationCommand()
@ -139,7 +139,7 @@ def limit_size(x: str) -> str:
def merge_osc_99(prev: NotificationCommand, cmd: NotificationCommand) -> NotificationCommand: def merge_osc_99(prev: NotificationCommand, cmd: NotificationCommand) -> NotificationCommand:
if prev.done or prev.identifier != cmd.identifier: if prev.done or prev.identifier != cmd.identifier:
return cmd return cmd
cmd.actions = limit_size(prev.actions + ',' + cmd.actions) cmd.actions = limit_size(f'{prev.actions},{cmd.actions}')
cmd.title = limit_size(prev.title + cmd.title) cmd.title = limit_size(prev.title + cmd.title)
cmd.body = limit_size(prev.body + cmd.body) cmd.body = limit_size(prev.body + cmd.body)
return cmd return cmd
@ -198,7 +198,7 @@ def notify_with_command(cmd: NotificationCommand, window_id: int, notify_impleme
title = cmd.title or cmd.body title = cmd.title or cmd.body
body = cmd.body if cmd.title else '' body = cmd.body if cmd.title else ''
if title: if title:
identifier = 'i' + str(next(id_counter)) identifier = f'i{next(id_counter)}'
notify_implementation(title, body, identifier) notify_implementation(title, body, identifier)
register_identifier(identifier, cmd, window_id) register_identifier(identifier, cmd, window_id)

View File

@ -114,7 +114,7 @@ def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc:
path = unquoted_path.lower() path = unquoted_path.lower()
for ext in mc.value.split(','): for ext in mc.value.split(','):
ext = ext.strip() ext = ext.strip()
if path.endswith('.' + ext): if path.endswith(f'.{ext}'):
return True return True
return False return False

View File

@ -379,7 +379,7 @@ def parse_mods(parts: Iterable[str], sc: str) -> Optional[int]:
mods = 0 mods = 0
for m in parts: for m in parts:
try: try:
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper())) mods |= getattr(defines, f'GLFW_MOD_{map_mod(m.upper())}')
except AttributeError: except AttributeError:
if m.upper() != 'NONE': if m.upper() != 'NONE':
log_error(f'Shortcut: {sc} has unknown modifier, ignoring') log_error(f'Shortcut: {sc} has unknown modifier, ignoring')
@ -394,7 +394,7 @@ def to_modifiers(val: str) -> int:
def parse_shortcut(sc: str) -> SingleKey: def parse_shortcut(sc: str) -> SingleKey:
if sc.endswith('+') and len(sc) > 1: if sc.endswith('+') and len(sc) > 1:
sc = sc[:-1] + 'plus' sc = f'{sc[:-1]}plus'
parts = sc.split('+') parts = sc.split('+')
mods = 0 mods = 0
if len(parts) > 1: if len(parts) > 1:
@ -895,13 +895,13 @@ def resolve_aliases_and_parse_actions(
parts = rest.split(maxsplit=1) parts = rest.split(maxsplit=1)
if parts[0] != alias.name: if parts[0] != alias.name:
continue continue
new_defn = possible_alias + ' ' + alias.value + ((' ' + parts[1]) if len(parts) > 1 else '') new_defn = f'{possible_alias} {alias.value} {f" {parts[1]}" if len(parts) > 1 else ""}'
new_aliases = aliases.copy() new_aliases = aliases.copy()
new_aliases[possible_alias] = [a for a in aliases[possible_alias] if a is not alias] new_aliases[possible_alias] = [a for a in aliases[possible_alias] if a is not alias]
yield from resolve_aliases_and_parse_actions(new_defn, new_aliases, map_type) yield from resolve_aliases_and_parse_actions(new_defn, new_aliases, map_type)
return return
else: # action_alias else: # action_alias
new_defn = alias.value + ((' ' + rest) if rest else '') new_defn = f'{alias.value} {rest}' if rest else alias.value
new_aliases = aliases.copy() new_aliases = aliases.copy()
new_aliases.pop(possible_alias) new_aliases.pop(possible_alias)
yield from resolve_aliases_and_parse_actions(new_defn, new_aliases, map_type) yield from resolve_aliases_and_parse_actions(new_defn, new_aliases, map_type)
@ -909,7 +909,7 @@ def resolve_aliases_and_parse_actions(
if possible_alias == 'combine': if possible_alias == 'combine':
sep, rest = rest.split(maxsplit=1) sep, rest = rest.split(maxsplit=1)
parts = re.split(r'\s*' + re.escape(sep) + r'\s*', rest) parts = re.split(fr'\s*{re.escape(sep)}\s*', rest)
for x in parts: for x in parts:
if x: if x:
yield from resolve_aliases_and_parse_actions(x, aliases, map_type) yield from resolve_aliases_and_parse_actions(x, aliases, map_type)

View File

@ -1,5 +1,4 @@
// This file is generated by /home/kovid/work/kitty/./gen-apc-parsers.py do not // This file is generated by gen-apc-parsers.py do not edit!
// edit!
#pragma once #pragma once
@ -148,9 +147,9 @@ static inline void parse_graphics_code(Screen *screen,
case action: { case action: {
g.action = screen->parser_buf[pos++] & 0xff; g.action = screen->parser_buf[pos++] & 0xff;
if (g.action != 'q' && g.action != 'p' && g.action != 't' && if (g.action != 'T' && g.action != 'a' && g.action != 'c' &&
g.action != 'd' && g.action != 'c' && g.action != 'a' && g.action != 'd' && g.action != 'f' && g.action != 'p' &&
g.action != 'T' && g.action != 'f') { g.action != 'q' && g.action != 't') {
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
"value for action: 0x%x", "value for action: 0x%x",
g.action); g.action);
@ -160,16 +159,16 @@ static inline void parse_graphics_code(Screen *screen,
case delete_action: { case delete_action: {
g.delete_action = screen->parser_buf[pos++] & 0xff; g.delete_action = screen->parser_buf[pos++] & 0xff;
if (g.delete_action != 'q' && g.delete_action != 'Q' && if (g.delete_action != 'A' && g.delete_action != 'C' &&
g.delete_action != 'c' && g.delete_action != 'C' && g.delete_action != 'F' && g.delete_action != 'I' &&
g.delete_action != 'N' && g.delete_action != 'i' && g.delete_action != 'N' && g.delete_action != 'P' &&
g.delete_action != 'A' && g.delete_action != 'y' && g.delete_action != 'Q' && g.delete_action != 'X' &&
g.delete_action != 'a' && g.delete_action != 'I' && g.delete_action != 'Y' && g.delete_action != 'Z' &&
g.delete_action != 'F' && g.delete_action != 'p' && g.delete_action != 'a' && g.delete_action != 'c' &&
g.delete_action != 'z' && g.delete_action != 'x' && g.delete_action != 'f' && g.delete_action != 'i' &&
g.delete_action != 'n' && g.delete_action != 'X' && g.delete_action != 'n' && g.delete_action != 'p' &&
g.delete_action != 'Y' && g.delete_action != 'P' && g.delete_action != 'q' && g.delete_action != 'x' &&
g.delete_action != 'Z' && g.delete_action != 'f') { g.delete_action != 'y' && g.delete_action != 'z') {
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
"value for delete_action: 0x%x", "value for delete_action: 0x%x",
g.delete_action); g.delete_action);
@ -179,8 +178,8 @@ static inline void parse_graphics_code(Screen *screen,
case transmission_type: { case transmission_type: {
g.transmission_type = screen->parser_buf[pos++] & 0xff; g.transmission_type = screen->parser_buf[pos++] & 0xff;
if (g.transmission_type != 's' && g.transmission_type != 't' && if (g.transmission_type != 'd' && g.transmission_type != 'f' &&
g.transmission_type != 'd' && g.transmission_type != 'f') { g.transmission_type != 's' && g.transmission_type != 't') {
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
"value for transmission_type: 0x%x", "value for transmission_type: 0x%x",
g.transmission_type); g.transmission_type);

View File

@ -107,20 +107,20 @@ Do not send text to the active window, even if it is one of the matched windows.
if '\x04' in decoded_data: if '\x04' in decoded_data:
decoded_data = decoded_data[:decoded_data.index('\x04')] decoded_data = decoded_data[:decoded_data.index('\x04')]
keep_going = False keep_going = False
ret['data'] = 'text:' + decoded_data ret['data'] = f'text:{decoded_data}'
yield ret yield ret
else: else:
while True: while True:
data = sys.stdin.buffer.read(limit) data = sys.stdin.buffer.read(limit)
if not data: if not data:
break break
ret['data'] = 'base64:' + base64.standard_b64encode(data).decode('ascii') ret['data'] = f'base64:{base64.standard_b64encode(data).decode("ascii")}'
yield ret yield ret
def chunks(text: str) -> CmdGenerator: def chunks(text: str) -> CmdGenerator:
data = parse_send_text_bytes(text).decode('utf-8') data = parse_send_text_bytes(text).decode('utf-8')
while data: while data:
ret['data'] = 'text:' + data[:limit] ret['data'] = f'text:{data[:limit]}'
yield ret yield ret
data = data[limit:] data = data[limit:]
@ -130,7 +130,7 @@ Do not send text to the active window, even if it is one of the matched windows.
data = f.read(limit) data = f.read(limit)
if not data: if not data:
break break
ret['data'] = 'base64:' + base64.standard_b64encode(data).decode('ascii') ret['data'] = f'base64:{base64.standard_b64encode(data).decode("ascii")}'
yield ret yield ret
sources = [] sources = []

2
kitty/rgb.py generated
View File

@ -28,7 +28,7 @@ def parse_single_color(c: str) -> int:
def parse_sharp(spec: str) -> Optional[Color]: def parse_sharp(spec: str) -> Optional[Color]:
if len(spec) in (3, 6, 9, 12): if len(spec) in (3, 6, 9, 12):
part_len = len(spec) // 3 part_len = len(spec) // 3
colors = re.findall(r'[a-fA-F0-9]{%d}' % part_len, spec) colors = re.findall(fr'[a-fA-F0-9]{{{part_len}}}', spec)
return Color(*map(parse_single_color, colors)) return Color(*map(parse_single_color, colors))
return None return None

View File

@ -221,7 +221,7 @@ def real_main(global_opts: RCOptions) -> None:
if e.code != 0: if e.code != 0:
print(end=output_prefix, flush=True) print(end=output_prefix, flush=True)
print_err(e) print_err(e)
print_err('Use "{}" to see how to use this command.'.format(emph('help ' + cmd))) print_err('Use "{}" to see how to use this command.'.format(emph(f'help {cmd}')))
continue continue
except Exception: except Exception:
print(end=output_prefix, flush=True) print(end=output_prefix, flush=True)

View File

@ -102,14 +102,14 @@ class ColorFormatter:
ans = '9' ans = '9'
elif q == 'tab': elif q == 'tab':
col = color_from_int((self.draw_data.tab_bg if self.which == '4' else self.draw_data.tab_fg)(self.tab_data)) col = color_from_int((self.draw_data.tab_bg if self.which == '4' else self.draw_data.tab_fg)(self.tab_data))
ans = '8' + color_as_sgr(col) ans = f'8{color_as_sgr(col)}'
else: else:
if name.startswith('_'): if name.startswith('_'):
q = '#' + name[1:] q = f'#{name[1:]}'
c = to_color(q) c = to_color(q)
if c is None: if c is None:
raise AttributeError(f'{name} is not a valid color') raise AttributeError(f'{name} is not a valid color')
ans = '8' + color_as_sgr(c) ans = f'8{color_as_sgr(c)}'
return f'\x1b[{self.which}{ans}m' return f'\x1b[{self.which}{ans}m'

View File

@ -471,8 +471,8 @@ def get_capabilities(query_string: str, opts: 'Options') -> Generator[str, None,
def result(encoded_query_name: str, x: Optional[str] = None) -> str: def result(encoded_query_name: str, x: Optional[str] = None) -> str:
if x is None: if x is None:
return '0+r' + encoded_query_name return f'0+r{encoded_query_name}'
return '1+r' + encoded_query_name + '=' + hexlify(str(x).encode('utf-8')).decode('ascii') return f'1+r{encoded_query_name}={hexlify(str(x).encode("utf-8")).decode("ascii")}'
for encoded_query_name in query_string.split(';'): for encoded_query_name in query_string.split(';'):
name = qname = unhexlify(encoded_query_name).decode('utf-8') name = qname = unhexlify(encoded_query_name).decode('utf-8')

View File

@ -40,7 +40,7 @@ def echo_cmd(cmd: Iterable[str]) -> None:
isatty = sys.stdout.isatty() isatty = sys.stdout.isatty()
end = '\n' end = '\n'
if isatty: if isatty:
end = '\x1b[m' + end end = f'\x1b[m{end}'
print('\x1b[92m', end='') print('\x1b[92m', end='')
print(shlex.join(cmd), end=end, flush=True) print(shlex.join(cmd), end=end, flush=True)
@ -146,11 +146,11 @@ def run_website(args: Any) -> None:
def sign_file(path: str) -> None: def sign_file(path: str) -> None:
dest = path + '.sig' dest = f'{path}.sig'
with suppress(FileNotFoundError): with suppress(FileNotFoundError):
os.remove(dest) os.remove(dest)
subprocess.check_call([ subprocess.check_call([
os.environ['PENV'] + '/gpg-as-kovid', '--output', path + '.sig', os.environ['PENV'] + '/gpg-as-kovid', '--output', f'{path}.sig',
'--detach-sig', path '--detach-sig', path
]) ])
@ -159,7 +159,7 @@ def run_sdist(args: Any) -> None:
with tempfile.TemporaryDirectory() as tdir: with tempfile.TemporaryDirectory() as tdir:
base = os.path.join(tdir, f'kitty-{version}') base = os.path.join(tdir, f'kitty-{version}')
os.mkdir(base) os.mkdir(base)
subprocess.check_call('git archive HEAD | tar -x -C ' + base, shell=True) subprocess.check_call(f'git archive HEAD | tar -x -C {base}', shell=True)
dest = os.path.join(base, 'docs', '_build') dest = os.path.join(base, 'docs', '_build')
os.mkdir(dest) os.mkdir(dest)
for x in 'html man'.split(): for x in 'html man'.split():
@ -167,9 +167,9 @@ def run_sdist(args: Any) -> None:
dest = os.path.abspath(os.path.join('build', f'kitty-{version}.tar')) dest = os.path.abspath(os.path.join('build', f'kitty-{version}.tar'))
subprocess.check_call(['tar', '-cf', dest, os.path.basename(base)], cwd=tdir) subprocess.check_call(['tar', '-cf', dest, os.path.basename(base)], cwd=tdir)
with suppress(FileNotFoundError): with suppress(FileNotFoundError):
os.remove(dest + '.xz') os.remove(f'{dest}.xz')
subprocess.check_call(['xz', '-9', dest]) subprocess.check_call(['xz', '-9', dest])
sign_file(dest + '.xz') sign_file(f'{dest}.xz')
class ReadFileWithProgressReporting(io.FileIO): # {{{ class ReadFileWithProgressReporting(io.FileIO): # {{{
@ -231,7 +231,7 @@ class Base: # {{{
class GitHub(Base): # {{{ class GitHub(Base): # {{{
API = 'https://api.github.com/' API = 'https://api.github.com'
def __init__( def __init__(
self, self,
@ -244,12 +244,12 @@ class GitHub(Base): # {{{
): ):
self.files, self.reponame, self.version, self.username, self.password, self.replace = ( self.files, self.reponame, self.version, self.username, self.password, self.replace = (
files, reponame, version, username, password, replace) files, reponame, version, username, password, replace)
self.current_tag_name = self.version if self.version == 'nightly' else ('v' + self.version) self.current_tag_name = self.version if self.version == 'nightly' else f'v{self.version}'
self.is_nightly = self.current_tag_name == 'nightly' self.is_nightly = self.current_tag_name == 'nightly'
self.requests = s = requests.Session() self.requests = s = requests.Session()
s.auth = (self.username, self.password) s.auth = (self.username, self.password)
s.headers.update({'Accept': 'application/vnd.github.v3+json'}) s.headers.update({'Accept': 'application/vnd.github.v3+json'})
self.url_base = f'{self.API}repos/{self.username}/{self.reponame}/releases/' self.url_base = f'{self.API}/repos/{self.username}/{self.reponame}/releases'
def patch(self, url: str, fail_msg: str, **data: Any) -> None: def patch(self, url: str, fail_msg: str, **data: Any) -> None:
rdata = json.dumps(data) rdata = json.dumps(data)
@ -262,7 +262,7 @@ class GitHub(Base): # {{{
self.fail(r, fail_msg) self.fail(r, fail_msg)
def update_nightly_description(self, release_id: int) -> None: def update_nightly_description(self, release_id: int) -> None:
url = self.url_base + str(release_id) url = f'{self.url_base}/{release_id}'
now = str(datetime.datetime.utcnow()).split('.')[0] + ' UTC' now = str(datetime.datetime.utcnow()).split('.')[0] + ' UTC'
with open('.git/refs/heads/master') as f: with open('.git/refs/heads/master') as f:
commit = f.read().strip() commit = f.read().strip()
@ -276,7 +276,7 @@ class GitHub(Base): # {{{
# self.clean_older_releases(releases) # self.clean_older_releases(releases)
release = self.create_release() release = self.create_release()
upload_url = release['upload_url'].partition('{')[0] upload_url = release['upload_url'].partition('{')[0]
asset_url = self.url_base + 'assets/{}' asset_url = f'{self.url_base}/assets/{{}}'
existing_assets = self.existing_assets(release['id']) existing_assets = self.existing_assets(release['id'])
if self.is_nightly: if self.is_nightly:
for fname in existing_assets: for fname in existing_assets:
@ -308,7 +308,7 @@ class GitHub(Base): # {{{
self.info(f'\nDeleting old released installers from: {release["tag_name"]}') self.info(f'\nDeleting old released installers from: {release["tag_name"]}')
for asset in release['assets']: for asset in release['assets']:
r = self.requests.delete( r = self.requests.delete(
f'{self.API}repos/{self.username}/{self.reponame}/releases/assets/{asset["id"]}') f'{self.url_base}/assets/{asset["id"]}')
if r.status_code != 204: if r.status_code != 204:
self.fail(r, f'Failed to delete obsolete asset: {asset["name"]} for release: {release["tag_name"]}') self.fail(r, f'Failed to delete obsolete asset: {asset["name"]} for release: {release["tag_name"]}')
@ -336,7 +336,7 @@ class GitHub(Base): # {{{
return bool(error_code == 'already_exists') return bool(error_code == 'already_exists')
def existing_assets(self, release_id: str) -> Dict[str, str]: def existing_assets(self, release_id: str) -> Dict[str, str]:
url = f'{self.API}repos/{self.username}/{self.reponame}/releases/{release_id}/assets' url = f'{self.url_base}/{release_id}/assets'
r = self.requests.get(url) r = self.requests.get(url)
if r.status_code != 200: if r.status_code != 200:
self.fail(r, 'Failed to get assets for release') self.fail(r, 'Failed to get assets for release')
@ -345,15 +345,14 @@ class GitHub(Base): # {{{
def create_release(self) -> Dict[str, Any]: def create_release(self) -> Dict[str, Any]:
' Create a release on GitHub or if it already exists, return the existing release ' ' Create a release on GitHub or if it already exists, return the existing release '
# Check for existing release # Check for existing release
url = f'{self.API}repos/{self.username}/{self.reponame}/releases/tags/{self.current_tag_name}' url = f'{self.url_base}/tags/{self.current_tag_name}'
r = self.requests.get(url) r = self.requests.get(url)
if r.status_code == 200: if r.status_code == 200:
return dict(r.json()) return dict(r.json())
if self.is_nightly: if self.is_nightly:
raise SystemExit('No existing nightly release found on GitHub') raise SystemExit('No existing nightly release found on GitHub')
url = f'{self.API}repos/{self.username}/{self.reponame}/releases'
r = self.requests.post( r = self.requests.post(
url, self.url_base,
data=json.dumps({ data=json.dumps({
'tag_name': self.current_tag_name, 'tag_name': self.current_tag_name,
'target_commitish': 'master', 'target_commitish': 'master',
@ -394,7 +393,7 @@ def files_for_upload() -> Dict[str, str]:
files[f'build/kitty-{version}.tar.xz.sig'] = 'Source code GPG signature' files[f'build/kitty-{version}.tar.xz.sig'] = 'Source code GPG signature'
for path, desc in signatures.items(): for path, desc in signatures.items():
sign_file(path) sign_file(path)
files[path + '.sig'] = desc files[f'{path}.sig'] = desc
for f in files: for f in files:
if not os.path.exists(f): if not os.path.exists(f):
raise SystemExit(f'The release artifact {f} does not exist') raise SystemExit(f'The release artifact {f} does not exist')
@ -460,7 +459,7 @@ def exec_actions(actions: Iterable[str], args: Any) -> None:
for action in actions: for action in actions:
print('Running', action) print('Running', action)
cwd = os.getcwd() cwd = os.getcwd()
globals()['run_' + action](args) globals()[f'run_{action}'](args)
os.chdir(cwd) os.chdir(cwd)

View File

@ -710,7 +710,7 @@ def compile_c_extension(
def on_success() -> None: def on_success() -> None:
os.rename(dest, real_dest) os.rename(dest, real_dest)
compilation_database.add_command(desc, cmd, partial(newer, real_dest, *objects), on_success=on_success, key=CompileKey('', module + '.so')) compilation_database.add_command(desc, cmd, partial(newer, real_dest, *objects), on_success=on_success, key=CompileKey('', f'{module}.so'))
def find_c_files() -> Tuple[List[str], List[str]]: def find_c_files() -> Tuple[List[str], List[str]]:
@ -1112,7 +1112,7 @@ def create_macos_app_icon(where: str = 'Resources') -> None:
'iconutil', '-c', 'icns', iconset_dir, '-o', icns_dir 'iconutil', '-c', 'icns', iconset_dir, '-o', icns_dir
]) ])
except FileNotFoundError: except FileNotFoundError:
print(error('iconutil not found') + ', using png2icns (without retina support) to convert the logo', file=sys.stderr) print(f'{error("iconutil not found")}, using png2icns (without retina support) to convert the logo', file=sys.stderr)
subprocess.check_call([ subprocess.check_call([
'png2icns', icns_dir 'png2icns', icns_dir
] + [os.path.join(iconset_dir, logo) for logo in [ ] + [os.path.join(iconset_dir, logo) for logo in [