Skip to content

Commit

Permalink
Add an auto-updating wrapper script.
Browse files Browse the repository at this point in the history
 - Install and run the package in a venv.
  • Loading branch information
rblank committed Jan 9, 2025
1 parent 09fca3a commit ea54be1
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ __pycache__

/LICENSES.deps.txt
/_build/
/_venv*/
/dist/
/node_modules/
/tdoc/common/static.gen/
Expand Down
1 change: 1 addition & 0 deletions .hgignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __pycache__
syntax: regexp
^LICENSES.deps.txt$
^_build/
^_venv.*/
^dist/
^node_modules/
^tdoc/common/static\.gen/
Expand Down
2 changes: 1 addition & 1 deletion serve.bat
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@echo off
tdoc serve
tdocv.py serve
7 changes: 4 additions & 3 deletions serve.desktop
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
Name=Serve
Name=tdoc serve
Type=Application
GenericName=Start a local server
GenericName=Start a local t-doc server
Comment=
Icon=document-preview
Exec=cd "$(dirname "%k")" && "${HOME}/.local/bin/tdoc" serve
Exec=cd "$(dirname "%k")" && ./tdocv.py serve
Path=
Terminal=true
StartupNotify=false
13 changes: 7 additions & 6 deletions tdoc/common/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,15 @@ def print_serving(self):
def check_upgrade(self):
try:
upgrades, editable = pip_check_upgrades(self.cfg, __project__)
if __project__ not in upgrades: return
msg = self.cfg.ansi(
if editable or __project__ not in upgrades: return
cur_version = metadata.version(__project__)
new_version = upgrades[__project__]
util.write_upgrade_marker(cur_version, new_version)
msg = (self.cfg.ansi(
"@{LYELLOW}A t-doc upgrade is available:@{NORM} "
"%s @{CYAN}%s@{NORM} => @{CYAN}%s@{NORM}\n"
"See <@{LBLUE}https://t-doc.org/common/%s#upgrade@{NORM}>\n"
% (__project__, metadata.version(__project__),
upgrades[__project__],
'development' if editable else 'install'))
"@{BOLD}Restart the server to upgrade.@{NORM}\n")
% (__project__, cur_version, new_version))
with self.lock:
self.upgrade_msg = msg
if not self.building: self.cfg.stdout.write(msg)
Expand Down
51 changes: 51 additions & 0 deletions tdoc/common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
# SPDX-License-Identifier: MIT

import argparse
import contextlib
import functools
import os
import pathlib
import re
import shutil
import sys

from . import __project__

_colors = True
try:
Expand Down Expand Up @@ -86,3 +90,50 @@ def wrapper(argv=None, stdin=None, stdout=None, stderr=None):
stderr.write(f'{e}\n')
sys.exit(1)
return wrapper


def rmtree(path, base, err):
if path.relative_to(base) == pathlib.Path():
raise Exception(f"Not removing {path}")
def on_error(fn, path, e):
err.write(f"Removal: {fn}: {path}: {e}\n")
shutil.rmtree(path, onexc=on_error)


_upgrade_marker = 'tdoc.upgrade'


def write_upgrade_marker(cur_version, new_version):
if sys.prefix == sys.base_prefix: return # Not running in a venv
marker = pathlib.Path(sys.prefix) / _upgrade_marker
with contextlib.suppress(Exception):
marker.write_text(f'{__project__} {cur_version} {new_version}')


def check_upgrade(base, venv, builder):
try:
marker = venv / _upgrade_marker
pkg, cur, new = marker.read_text().strip().split(' ')[:3]
except Exception:
return
if new == cur: return
out = builder.stderr
out.write(ansi("@{LYELLOW}A t-doc upgrade is available:@{NORM} "
"%s @{CYAN}%s@{NORM} => @{CYAN}%s@{NORM}\n"
"@{LWHITE}Would you like to upgrade?@{NORM} ")
% (pkg, cur, new))
if input().lower() in ('y', 'yes', 'o', 'oui', 'j', 'ja'):
out.write(ansi("\n@{LMAGENTA}Upgrading...@{NORM}\n"))
tmp = venv.with_name(venv.name + '-old')
restore = False
try:
venv.rename(tmp)
restore = True
builder.create(venv)
rmtree(tmp, base, out)
except BaseException as e:
if restore:
rmtree(venv, base, out)
tmp.rename(venv)
if not isinstance(e, Exception): raise
out.write("\n")
66 changes: 66 additions & 0 deletions tdocv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python
# Copyright 2025 Remy Blank <[email protected]>
# SPDX-License-Identifier: MIT

import contextlib
import pathlib
import subprocess
import sys
import venv

# TODO: Support running a specific version


class EnvBuilder(venv.EnvBuilder):
def __init__(self, stderr):
super().__init__(with_pip=True)
self.stderr = stderr

def post_setup(self, ctx):
super().post_setup(ctx)
self.pip(ctx, 'install', 't-doc-common')

def pip(self, ctx, *args, json_output=False):
subprocess.run((ctx.env_exec_cmd, '-m', 'pip') + args, check=True,
stdin=subprocess.DEVNULL, stdout=self.stderr,
stderr=self.stderr)


def main(argv, stdin, stdout, stderr):
base = pathlib.Path(argv[0]).parent.resolve()
vdir = base / '_venv'

# Create the venv if it doesn't exist.
builder = EnvBuilder(stderr)
if not vdir.exists():
stderr.write("Creating venv...\n")
builder.create(vdir)
stderr.write("\n")

# Import from modules installed in the venv.
for lib in (vdir / 'lib').glob('python*.*'):
sys.path.append(lib / 'site-packages')
with contextlib.suppress(ImportError):
from tdoc.common import util
break
else:
raise Exception("Failed to import tdoc.common.util")

# Upgrade if available and requested by the user.
util.check_upgrade(base, vdir, builder)

# Run the command.
subprocess.run([vdir / 'bin' / 'tdoc'] + argv[1:], check=True, cwd=base)


if __name__ == '__main__':
try:
sys.exit(main(sys.argv, sys.stdin, sys.stdout, sys.stderr))
except SystemExit:
raise
except BaseException as e:
if '--debug' in sys.argv:
raise
if not isinstance(e, KeyboardInterrupt):
sys.stderr.write(f'\n{e}\n')
sys.exit(1)

0 comments on commit ea54be1

Please sign in to comment.