Last active
December 4, 2019 15:48
-
-
Save eamirgh/b04c42f63c66d5be9397705e2f1ffb5a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# | |
# installer.sh | |
# Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net> | |
# | |
# Distributed under terms of the GPLv3 license. | |
# | |
python=$(command -v python3) | |
if [ -z "$python" ]; then | |
python=$(command -v python2) | |
fi | |
if [ -z "$python" ]; then | |
python=$(command -v python2.7) | |
fi | |
if [ -z "$python" ]; then | |
python=$(command -v python) | |
fi | |
if [ -z "$python" ]; then | |
python=python | |
fi | |
echo Using python executable: $python | |
$python -c "import sys; script_launch=lambda:sys.exit('Download of installer failed!'); exec(sys.stdin.read()); script_launch()" "$@" <<'INSTALLER_HEREDOC' | |
# {{{ | |
# HEREDOC_START | |
#!/usr/bin/env python3 | |
# vim:fileencoding=utf-8 | |
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> | |
from __future__ import ( | |
absolute_import, division, print_function, unicode_literals | |
) | |
import atexit | |
import json | |
import os | |
import platform | |
import re | |
import shlex | |
import shutil | |
import subprocess | |
import sys | |
import tempfile | |
py3 = sys.version_info[0] > 2 | |
is64bit = platform.architecture()[0] == '64bit' | |
is_macos = 'darwin' in sys.platform.lower() | |
if is_macos: | |
mac_ver = tuple(map(int, platform.mac_ver()[0].split('.'))) | |
if mac_ver[:2] < (10, 12): | |
raise SystemExit('Your version of macOS is too old, at least 10.12 is required') | |
try: | |
__file__ | |
from_file = True | |
except NameError: | |
from_file = False | |
if py3: | |
unicode = str | |
raw_input = input | |
import urllib.request as urllib | |
def encode_for_subprocess(x): | |
return x | |
else: | |
from future_builtins import map | |
import urllib2 as urllib | |
def encode_for_subprocess(x): | |
if isinstance(x, unicode): | |
x = x.encode('utf-8') | |
return x | |
def run(*args): | |
if len(args) == 1: | |
args = shlex.split(args[0]) | |
args = list(map(encode_for_subprocess, args)) | |
ret = subprocess.Popen(args).wait() | |
if ret != 0: | |
raise SystemExit(ret) | |
class Reporter: # {{{ | |
def __init__(self, fname): | |
self.fname = fname | |
self.last_percent = 0 | |
def __call__(self, blocks, block_size, total_size): | |
percent = (blocks*block_size)/float(total_size) | |
report = '\rDownloaded {:.1%} '.format(percent) | |
if percent - self.last_percent > 0.05: | |
self.last_percent = percent | |
print(report, end='') | |
sys.stdout.flush() | |
# }}} | |
def get_latest_release_data(): | |
print('Checking for latest release on GitHub...') | |
req = urllib.Request('https://api.github.com/repos/kovidgoyal/kitty/releases/latest', headers={'Accept': 'application/vnd.github.v3+json'}) | |
try: | |
res = urllib.urlopen(req).read().decode('utf-8') | |
except Exception as err: | |
raise SystemExit('Failed to contact {} with error: {}'.format(req.get_full_url(), err)) | |
data = json.loads(res) | |
html_url = data['html_url'].replace('/tag/', '/download/').rstrip('/') | |
for asset in data.get('assets', ()): | |
name = asset['name'] | |
if is_macos: | |
if name.endswith('.dmg'): | |
return html_url + '/' + name, asset['size'] | |
else: | |
if name.endswith('.txz'): | |
if is64bit: | |
if name.endswith('-x86_64.txz'): | |
return html_url + '/' + name, asset['size'] | |
else: | |
if name.endswith('-i686.txz'): | |
return html_url + '/' + name, asset['size'] | |
raise SystemExit('Failed to find the installer package on github') | |
def do_download(url, size, dest): | |
print('Will download and install', os.path.basename(dest)) | |
reporter = Reporter(os.path.basename(dest)) | |
# Get content length and check if range is supported | |
rq = urllib.urlopen(url) | |
headers = rq.info() | |
sent_size = int(headers['content-length']) | |
if sent_size != size: | |
raise SystemExit('Failed to download from {} Content-Length ({}) != {}'.format(url, sent_size, size)) | |
with open(dest, 'wb') as f: | |
while f.tell() < size: | |
raw = rq.read(8192) | |
if not raw: | |
break | |
f.write(raw) | |
reporter(f.tell(), 1, size) | |
rq.close() | |
if os.path.getsize(dest) < size: | |
raise SystemExit('Download failed, try again later') | |
print('\rDownloaded {} bytes'.format(os.path.getsize(dest))) | |
def clean_cache(cache, fname): | |
for x in os.listdir(cache): | |
if fname not in x: | |
os.remove(os.path.join(cache, x)) | |
def download_installer(url, size): | |
fname = url.rpartition('/')[-1] | |
tdir = tempfile.gettempdir() | |
cache = os.path.join(tdir, 'kitty-installer-cache') | |
if not os.path.exists(cache): | |
os.makedirs(cache) | |
clean_cache(cache, fname) | |
dest = os.path.join(cache, fname) | |
if os.path.exists(dest) and os.path.getsize(dest) == size: | |
print('Using previously downloaded', fname) | |
return dest | |
if os.path.exists(dest): | |
os.remove(dest) | |
do_download(url, size, dest) | |
return dest | |
def macos_install(dmg, dest='/Applications', launch=True): | |
mp = tempfile.mkdtemp() | |
atexit.register(shutil.rmtree, mp) | |
run('hdiutil', 'attach', dmg, '-mountpoint', mp) | |
try: | |
os.chdir(mp) | |
app = 'kitty.app' | |
d = os.path.join(dest, app) | |
if os.path.exists(d): | |
shutil.rmtree(d) | |
dest = os.path.join(dest, app) | |
run('ditto', '-v', app, dest) | |
print('Successfully installed kitty into', dest) | |
if launch: | |
run('open', dest) | |
finally: | |
os.chdir('/') | |
run('hdiutil', 'detach', mp) | |
def linux_install(installer, dest=os.path.expanduser('~/.local'), launch=True): | |
dest = os.path.join(dest, 'kitty.app') | |
if os.path.exists(dest): | |
shutil.rmtree(dest) | |
os.makedirs(dest) | |
print('Extracting tarball...') | |
run('tar', '-C', dest, '-xJof', installer) | |
run('cp /home/eamirgh/Code/clones/harfbuzz/src/.libs/libharfbuzz.so.0.20600.4 /opt/kitty.app/lib/libharfbuzz.so.0') | |
print('kitty successfully installed to', dest) | |
kitty = os.path.join(dest, 'bin', 'kitty') | |
print('Use', kitty, 'to run kitty') | |
if launch: | |
run(kitty, '--detach') | |
def main(dest=None, launch=True, installer=None): | |
if not dest: | |
if is_macos: | |
dest = '/Applications' | |
else: | |
dest = os.path.expanduser('~/.local') | |
machine = os.uname()[4] | |
if machine and machine.lower().startswith('arm'): | |
raise SystemExit( | |
'You are running on an ARM system. The kitty binaries are only' | |
' available for x86 systems. You will have to build from' | |
' source.') | |
if not installer: | |
url, size = get_latest_release_data() | |
installer = download_installer(url, size) | |
else: | |
installer = os.path.abspath(installer) | |
if not os.access(installer, os.R_OK): | |
raise SystemExit('Could not read from: {}'.format(installer)) | |
if is_macos: | |
macos_install(installer, dest=dest, launch=launch) | |
else: | |
linux_install(installer, dest=dest, launch=launch) | |
def script_launch(): | |
# To test: python3 -c "import runpy; runpy.run_path('installer.py', run_name='script_launch')" | |
def path(x): | |
return os.path.expandvars(os.path.expanduser(x)) | |
def to_bool(x): | |
return x.lower() in {'y', 'yes', '1', 'true'} | |
type_map = {x: path for x in 'dest installer'.split()} | |
type_map['launch'] = to_bool | |
kwargs = {} | |
for arg in sys.argv[1:]: | |
if arg: | |
m = re.match('([a-z_]+)=(.+)', arg) | |
if m is None: | |
raise SystemExit('Unrecognized command line argument: ' + arg) | |
k = m.group(1) | |
if k not in type_map: | |
raise SystemExit('Unrecognized command line argument: ' + arg) | |
kwargs[k] = type_map[k](m.group(2)) | |
main(**kwargs) | |
def update_intaller_wrapper(): | |
# To run: python3 -c "import runpy; runpy.run_path('installer.py', run_name='update_wrapper')" installer.sh | |
with open(__file__, 'rb') as f: | |
src = f.read().decode('utf-8') | |
wrapper = sys.argv[-1] | |
with open(wrapper, 'r+b') as f: | |
raw = f.read().decode('utf-8') | |
nraw = re.sub(r'^# HEREDOC_START.+^# HEREDOC_END', lambda m: '# HEREDOC_START\n{}\n# HEREDOC_END'.format(src), raw, flags=re.MULTILINE | re.DOTALL) | |
if 'update_intaller_wrapper()' not in nraw: | |
raise SystemExit('regex substitute of HEREDOC failed') | |
f.seek(0), f.truncate() | |
f.write(nraw.encode('utf-8')) | |
if __name__ == '__main__' and from_file: | |
main() | |
elif __name__ == 'update_wrapper': | |
update_intaller_wrapper() | |
elif __name__ == 'script_launch': | |
script_launch() | |
# HEREDOC_END | |
# }}} | |
INSTALLER_HEREDOC |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment