Compare commits

...

6 commits

4 changed files with 126 additions and 79 deletions

View file

@ -31,6 +31,10 @@ docs/viv.py: src/viv/viv.py
docs/index.md: README.md
cp $< $@
examples/black: .FORCE
rm -f $@
viv shim black -s -f -o $@
clean: ## remove build artifacts
rm -rf {build,dist}
@ -39,4 +43,7 @@ generate-example-vivens: ##
for f in $(EXAMPLES); \
do python examples/$$f; done
.FORCE:
.PHONY: .FORCE
-include .task.cfg.mk

View file

@ -1,4 +1,4 @@
<svg class="rich-terminal shadow" viewBox="0 0 890.3333333333334 277.5333333333333" xmlns="http://www.w3.org/2000/svg">
<svg class="rich-terminal shadow" viewBox="0 0 890.3333333333334 301.93333333333334" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io & yartsu https://github.com/daylinmorgan/yartsu -->
<style>
@ -19,14 +19,14 @@
font-weight: 700;
}
.terminal-3499396704-matrix {
.terminal-1773455935-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.terminal-3499396704-title {
.terminal-1773455935-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
@ -36,56 +36,60 @@
-webkit-filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
}
.terminal-3499396704-r1 { fill: #94e2d5;font-weight: bold }
.terminal-3499396704-r2 { fill: #c6d0f5 }
.terminal-3499396704-r3 { fill: #f9e2af;font-weight: bold }
.terminal-1773455935-r1 { fill: #94e2d5;font-weight: bold }
.terminal-1773455935-r2 { fill: #c6d0f5 }
.terminal-1773455935-r3 { fill: #f9e2af;font-weight: bold }
</style>
<defs>
<clipPath id="terminal-3499396704-clip-terminal">
<rect x="0" y="0" width="853.0" height="194.2" />
<clipPath id="terminal-1773455935-clip-terminal">
<rect x="0" y="0" width="853.0" height="218.6" />
</clipPath>
<clipPath id="terminal-3499396704-line-0">
<clipPath id="terminal-1773455935-line-0">
<rect x="0" y="1.5" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-3499396704-line-1">
<clipPath id="terminal-1773455935-line-1">
<rect x="0" y="25.9" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-3499396704-line-2">
<clipPath id="terminal-1773455935-line-2">
<rect x="0" y="50.3" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-3499396704-line-3">
<clipPath id="terminal-1773455935-line-3">
<rect x="0" y="74.7" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-3499396704-line-4">
<clipPath id="terminal-1773455935-line-4">
<rect x="0" y="99.1" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-3499396704-line-5">
<clipPath id="terminal-1773455935-line-5">
<rect x="0" y="123.5" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-3499396704-line-6">
<clipPath id="terminal-1773455935-line-6">
<rect x="0" y="147.9" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1773455935-line-7">
<rect x="0" y="172.3" width="854" height="24.65"/>
</clipPath>
</defs>
<rect fill="#1e1e2e" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="10.1667" y="1" width="870" height="243.2" rx="8"/><text class="terminal-3499396704-title" fill="#c6d0f5" text-anchor="middle" x="435" y="27">viv&#160;list&#160;--help</text>
<rect fill="#1e1e2e" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="10.1667" y="1" width="870" height="267.6" rx="8"/><text class="terminal-1773455935-title" fill="#c6d0f5" text-anchor="middle" x="435" y="27">viv&#160;list&#160;--help</text>
<g transform="translate(32,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(18.166666666666664, 41) scale(.95)" clip-path="url(#terminal-3499396704-clip-terminal)">
<g transform="translate(18.166666666666664, 41) scale(.95)" clip-path="url(#terminal-1773455935-clip-terminal)">
<g class="terminal-3499396704-matrix">
<text class="terminal-3499396704-r1" x="0" y="20" textLength="61" clip-path="url(#terminal-3499396704-line-0)">usage</text><text class="terminal-3499396704-r2" x="61" y="20" textLength="305" clip-path="url(#terminal-3499396704-line-0)">:&#160;viv&#160;list&#160;[-h]&#160;[-v]&#160;[-q]</text><text class="terminal-3499396704-r2" x="854" y="20" textLength="12.2" clip-path="url(#terminal-3499396704-line-0)">
</text><text class="terminal-3499396704-r2" x="854" y="44.4" textLength="12.2" clip-path="url(#terminal-3499396704-line-1)">
</text><text class="terminal-3499396704-r2" x="0" y="68.8" textLength="195.2" clip-path="url(#terminal-3499396704-line-2)">list&#160;all&#160;vivenvs</text><text class="terminal-3499396704-r2" x="854" y="68.8" textLength="12.2" clip-path="url(#terminal-3499396704-line-2)">
</text><text class="terminal-3499396704-r2" x="854" y="93.2" textLength="12.2" clip-path="url(#terminal-3499396704-line-3)">
</text><text class="terminal-3499396704-r1" x="0" y="117.6" textLength="85.4" clip-path="url(#terminal-3499396704-line-4)">options</text><text class="terminal-3499396704-r2" x="85.4" y="117.6" textLength="12.2" clip-path="url(#terminal-3499396704-line-4)">:</text><text class="terminal-3499396704-r2" x="854" y="117.6" textLength="12.2" clip-path="url(#terminal-3499396704-line-4)">
</text><text class="terminal-3499396704-r3" x="24.4" y="142" textLength="24.4" clip-path="url(#terminal-3499396704-line-5)">-h</text><text class="terminal-3499396704-r2" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-3499396704-line-5)">,&#160;</text><text class="terminal-3499396704-r3" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-3499396704-line-5)">--help</text><text class="terminal-3499396704-r2" x="146.4" y="142" textLength="463.6" clip-path="url(#terminal-3499396704-line-5)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;show&#160;this&#160;help&#160;message&#160;and&#160;exit</text><text class="terminal-3499396704-r2" x="854" y="142" textLength="12.2" clip-path="url(#terminal-3499396704-line-5)">
</text><text class="terminal-3499396704-r3" x="24.4" y="166.4" textLength="24.4" clip-path="url(#terminal-3499396704-line-6)">-v</text><text class="terminal-3499396704-r2" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-3499396704-line-6)">,&#160;</text><text class="terminal-3499396704-r3" x="73.2" y="166.4" textLength="109.8" clip-path="url(#terminal-3499396704-line-6)">--verbose</text><text class="terminal-3499396704-r2" x="183" y="166.4" textLength="414.8" clip-path="url(#terminal-3499396704-line-6)">&#160;&#160;&#160;&#160;show&#160;full&#160;metadata&#160;for&#160;vivenvs</text><text class="terminal-3499396704-r2" x="854" y="166.4" textLength="12.2" clip-path="url(#terminal-3499396704-line-6)">
</text><text class="terminal-3499396704-r3" x="24.4" y="190.8" textLength="24.4" clip-path="url(#terminal-3499396704-line-7)">-q</text><text class="terminal-3499396704-r2" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-3499396704-line-7)">,&#160;</text><text class="terminal-3499396704-r3" x="73.2" y="190.8" textLength="85.4" clip-path="url(#terminal-3499396704-line-7)">--quiet</text><text class="terminal-3499396704-r2" x="158.6" y="190.8" textLength="427" clip-path="url(#terminal-3499396704-line-7)">&#160;&#160;&#160;&#160;&#160;&#160;suppress&#160;non-essential&#160;output</text><text class="terminal-3499396704-r2" x="854" y="190.8" textLength="12.2" clip-path="url(#terminal-3499396704-line-7)">
<g class="terminal-1773455935-matrix">
<text class="terminal-1773455935-r1" x="0" y="20" textLength="61" clip-path="url(#terminal-1773455935-line-0)">usage</text><text class="terminal-1773455935-r2" x="61" y="20" textLength="414.8" clip-path="url(#terminal-1773455935-line-0)">:&#160;viv&#160;list&#160;[-h]&#160;[-f]&#160;[-q]&#160;[--json]</text><text class="terminal-1773455935-r2" x="854" y="20" textLength="12.2" clip-path="url(#terminal-1773455935-line-0)">
</text><text class="terminal-1773455935-r2" x="854" y="44.4" textLength="12.2" clip-path="url(#terminal-1773455935-line-1)">
</text><text class="terminal-1773455935-r2" x="0" y="68.8" textLength="195.2" clip-path="url(#terminal-1773455935-line-2)">list&#160;all&#160;vivenvs</text><text class="terminal-1773455935-r2" x="854" y="68.8" textLength="12.2" clip-path="url(#terminal-1773455935-line-2)">
</text><text class="terminal-1773455935-r2" x="854" y="93.2" textLength="12.2" clip-path="url(#terminal-1773455935-line-3)">
</text><text class="terminal-1773455935-r1" x="0" y="117.6" textLength="85.4" clip-path="url(#terminal-1773455935-line-4)">options</text><text class="terminal-1773455935-r2" x="85.4" y="117.6" textLength="12.2" clip-path="url(#terminal-1773455935-line-4)">:</text><text class="terminal-1773455935-r2" x="854" y="117.6" textLength="12.2" clip-path="url(#terminal-1773455935-line-4)">
</text><text class="terminal-1773455935-r3" x="24.4" y="142" textLength="24.4" clip-path="url(#terminal-1773455935-line-5)">-h</text><text class="terminal-1773455935-r2" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-1773455935-line-5)">,&#160;</text><text class="terminal-1773455935-r3" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-1773455935-line-5)">--help</text><text class="terminal-1773455935-r2" x="146.4" y="142" textLength="439.2" clip-path="url(#terminal-1773455935-line-5)">&#160;&#160;&#160;&#160;&#160;show&#160;this&#160;help&#160;message&#160;and&#160;exit</text><text class="terminal-1773455935-r2" x="854" y="142" textLength="12.2" clip-path="url(#terminal-1773455935-line-5)">
</text><text class="terminal-1773455935-r3" x="24.4" y="166.4" textLength="24.4" clip-path="url(#terminal-1773455935-line-6)">-f</text><text class="terminal-1773455935-r2" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-1773455935-line-6)">,&#160;</text><text class="terminal-1773455935-r3" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-1773455935-line-6)">--full</text><text class="terminal-1773455935-r2" x="146.4" y="166.4" textLength="427" clip-path="url(#terminal-1773455935-line-6)">&#160;&#160;&#160;&#160;&#160;show&#160;full&#160;metadata&#160;for&#160;vivenvs</text><text class="terminal-1773455935-r2" x="854" y="166.4" textLength="12.2" clip-path="url(#terminal-1773455935-line-6)">
</text><text class="terminal-1773455935-r3" x="24.4" y="190.8" textLength="24.4" clip-path="url(#terminal-1773455935-line-7)">-q</text><text class="terminal-1773455935-r2" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-1773455935-line-7)">,&#160;</text><text class="terminal-1773455935-r3" x="73.2" y="190.8" textLength="85.4" clip-path="url(#terminal-1773455935-line-7)">--quiet</text><text class="terminal-1773455935-r2" x="158.6" y="190.8" textLength="402.6" clip-path="url(#terminal-1773455935-line-7)">&#160;&#160;&#160;&#160;suppress&#160;non-essential&#160;output</text><text class="terminal-1773455935-r2" x="854" y="190.8" textLength="12.2" clip-path="url(#terminal-1773455935-line-7)">
</text><text class="terminal-1773455935-r3" x="24.4" y="215.2" textLength="73.2" clip-path="url(#terminal-1773455935-line-8)">--json</text><text class="terminal-1773455935-r2" x="97.6" y="215.2" textLength="475.8" clip-path="url(#terminal-1773455935-line-8)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;name:metadata&#160;json&#160;for&#160;vivenvs</text><text class="terminal-1773455935-r2" x="854" y="215.2" textLength="12.2" clip-path="url(#terminal-1773455935-line-8)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1,26 +1,43 @@
#!/usr/bin/env python3
# fmt: off
def _viv_use(*pkgs, track_exe=False, name=""): # noqa
T,F,N=True,False,None;i,s,m,spec=__import__,str,map,[*pkgs] # noqa
e,w=lambda x: T if x else F,lambda p,t: p.write_text(t) # noqa
if not {*m(type,pkgs)}=={s}: raise ValueError(f"spec: {pkgs} is invalid") # noqa
ge,sys,P,ew=i("os").getenv,i("sys"),i("pathlib").Path,i("sys").stderr.write # noqa
(cache:=(P(ge("XDG_CACHE_HOME",P.home()/".cache"))/"viv"/"venvs")).mkdir(parents=T,exist_ok=T) # noqa
((sha256:=i("hashlib").sha256()).update((s(spec)+ # noqa
(((exe:=("N/A",s(P(i("sys").executable).resolve()))[e(track_exe)])))).encode())) # noqa
if {env:=cache/(((_id:=sha256.hexdigest()),name)[e(name)])}-{*cache.glob("*/")} or ge("VIV_FORCE"): # noqa
v=e(ge("VIV_VERBOSE"));ew(f"generating new vivenv -> {env.name}\n") # noqa
i("venv").EnvBuilder(with_pip=T,clear=T).create(env) # noqa
w(env/"pip.conf","[global]\ndisable-pip-version-check=true") # noqa
if (rc:=(p:=i("subprocess").run([env/"bin"/"pip","install","--force-reinstall",*spec],text=T, # noqa
stdout=(-1,N)[v],stderr=(-2,N)[v])).returncode)!=0: # noqa
if env.is_dir():i("shutil").rmtree(env) # noqa
ew(f"pip had non zero exit ({rc})\n{p.stdout}\n");sys.exit(rc) # noqa
w(env/"viv-info.json",i("json").dumps( # noqa
{"created":s(i("datetime").datetime.today()),"id":_id,"spec":spec,"exe":exe})) # noqa
sys.path=[p for p in (*sys.path,s(*(env/"lib").glob("py*/si*")))if p!=i("site").USER_SITE] # noqa
return env # noqa
# fmt: on
def _viv_use(*pkgs, track_exe=False, name=""):
import hashlib, json, os, site, shutil, sys, venv # noqa
from pathlib import Path # noqa
from datetime import datetime # noqa
from subprocess import run # noqa
if not {*map(type, pkgs)} == {str}:
raise ValueError(f"spec: {pkgs} is invalid")
meta = dict.fromkeys(("created", "accessed"), (t := str(datetime.today())))
runner = str(Path(__file__).absolute().resolve())
force, verbose, xdg = map(os.getenv, ("VIV_FORCE", "VIV_VERBOSE", "XDG_CACHE_HOME"))
cache = (Path(xdg) if xdg else Path.home() / ".cache") / "viv" / "venvs"
cache.mkdir(parents=True, exist_ok=True)
exe = str(Path(sys.executable).resolve()) if track_exe else "N/A"
(sha256 := hashlib.sha256()).update((str(spec := [*pkgs]) + exe).encode())
_id = sha256.hexdigest()
if (env := cache / (name if name else _id)) not in cache.glob("*/") or force:
sys.stderr.write(f"generating new vivenv -> {env.name}\n")
venv.EnvBuilder(with_pip=True, clear=True).create(env)
(env / "pip.conf").write_text("[global]\ndisable-pip-version-check=true")
run_kw = dict(zip(("stdout", "stderr"), ((None,) * 2 if verbose else (-1, 2))))
p = run([env / "bin" / "pip", "install", "--force-reinstall", *spec], **run_kw)
if (rc := p.returncode) != 0:
if env.is_dir():
shutil.rmtree(env)
sys.stderr.write(f"pip had non zero exit ({rc})\n{p.stdout.decode()}\n")
sys.exit(rc)
meta.update(dict(id=_id, spec=spec, exe=exe, name=name, files=[runner]))
else:
meta = json.loads((env / "vivmeta.json").read_text())
meta.update(dict(accessed=t, files=sorted({*meta["files"], runner})))
(env / "vivmeta.json").write_text(json.dumps(meta))
sys.path = [p for p in sys.path if not p != site.USER_SITE]
site.addsitedir(str(*(env / "lib").glob("py*/si*")))
return env
import subprocess
import sys

View file

@ -50,7 +50,7 @@ from typing import (
from urllib.error import HTTPError
from urllib.request import urlopen
__version__ = "23.5a4-21-gf5c91fe-dev"
__version__ = "23.5a4-27-g1c2e5a8-dev"
class Config:
@ -746,20 +746,20 @@ class ViVenv:
verbose=bool(os.getenv("VIV_VERBOSE")),
)
def show(self, verbose: bool = False) -> None:
if not verbose:
_id = (
self.meta.id[:8]
if self.meta.id == self.name
else (self.name[:5] + "..." if len(self.name) > 8 else self.name)
)
def touch(self) -> None:
self.meta.accessed = str(datetime.today())
sys.stdout.write(
f"""{a.bold}{a.cyan}{_id}{a.end} """
f"""{a.style(", ".join(self.meta.spec),'dim')}\n"""
)
else:
self.tree()
def show(self) -> None:
_id = (
self.meta.id[:8]
if self.meta.id == self.name
else (self.name[:5] + "..." if len(self.name) > 8 else self.name)
)
sys.stdout.write(
f"""{a.bold}{a.cyan}{_id}{a.end} """
f"""{a.style(", ".join(self.meta.spec),'dim')}\n"""
)
def _tree_leaves(self, items: List[str], indent: str = "") -> str:
tree_chars = [""] * (len(items) - 1) + [""]
@ -981,6 +981,9 @@ class Viv:
else:
echo("re-using existing vivenv")
vivenv.touch()
vivenv.meta.write()
echo("see below for import statements\n")
if args.standalone:
@ -999,9 +1002,16 @@ class Viv:
sys.stdout.write("\n".join(self.vivenvs) + "\n")
elif len(self.vivenvs) == 0:
echo("no vivenvs setup")
elif args.full:
for _, vivenv in self.vivenvs.items():
vivenv.tree()
elif args.json:
sys.stdout.write(
json.dumps({k: v.meta.__dict__ for k, v in self.vivenvs.items()})
)
else:
for _, vivenv in self.vivenvs.items():
vivenv.show(args.verbose)
vivenv.show()
def exe(self, args: Namespace) -> None:
"""run python/pip in existing vivenv"""
@ -1028,8 +1038,10 @@ class Viv:
if not metadata_file.is_file():
error(f"Unable to find metadata for vivenv: {args.vivenv}", code=1)
vivenv.show(verbose=True)
if args.json:
sys.stdout.write(json.dumps(vivenv.meta.__dict__))
else:
vivenv.tree()
def _install_local_src(self, sha256: str, src: Path, cli: Path) -> None:
echo("updating local source copy of viv")
@ -1206,7 +1218,9 @@ class Viv:
else:
vivenv.create()
vivenv.install_pkgs()
vivenv.meta.write()
vivenv.touch()
vivenv.meta.write()
sys.exit(subprocess.run([vivenv.path / "bin" / bin, *args.rest]).returncode)
@ -1287,13 +1301,22 @@ class Viv:
subparsers = parser.add_subparsers(
metavar="<sub-cmd>", title="subcommands", required=True
)
p_vivenv_arg = ArgumentParser(add_help=False)
p_vivenv_arg.add_argument("vivenv", help="name/hash of vivenv")
p_list = self._get_subcmd_parser(subparsers, "list")
(p_json := ArgumentParser(add_help=False)).add_argument(
"--json",
help="name:metadata json for vivenvs ",
action="store_true",
default=False,
)
p_list = self._get_subcmd_parser(subparsers, "list", parents=[p_json])
p_list.add_argument(
"-v",
"--verbose",
"-f",
"--full",
help="show full metadata for vivenvs",
default=False,
action="store_true",
@ -1373,7 +1396,7 @@ class Viv:
self._get_subcmd_parser(
subparsers,
"info",
parents=[p_vivenv_arg],
parents=[p_vivenv_arg, p_json],
)
p_manage_shared = ArgumentParser(add_help=False)
p_manage_shared.add_argument(
@ -1428,9 +1451,12 @@ class Viv:
p_manage_sub.add_parser(
"purge", help="remove traces of viv", aliases="p", parents=[p_manage_shared]
).set_defaults(func=self.manage, cmd="purge")
(p_run_shim_shared := ArgumentParser(add_help=False)).add_argument(
"-b", "--bin", help="console_script/script to invoke", metavar="<bin>"
)
p_shim = self._get_subcmd_parser(
subparsers, "shim", parents=[p_freeze_shim_shared]
subparsers, "shim", parents=[p_freeze_shim_shared, p_run_shim_shared]
)
p_shim.add_argument(
@ -1446,11 +1472,8 @@ class Viv:
type=Path,
metavar="<path>",
)
p_shim.add_argument(
"-b", "--bin", help="console_script/script to invoke", metavar="<bin>"
)
p_run = self._get_subcmd_parser(subparsers, "run")
p_run = self._get_subcmd_parser(subparsers, "run", parents=[p_run_shim_shared])
p_run.add_argument(
"-r",
@ -1466,10 +1489,6 @@ class Viv:
)
p_run.add_argument("reqs", help="requirements specifiers", nargs="*")
p_run.add_argument(
"-b", "--bin", help="console_script/script to invoke", metavar="<bin>"
)
if "--" in sys.argv:
i = sys.argv.index("--")
args = parser.parse_args(sys.argv[1:i])