no time for conventions only development

This commit is contained in:
Daylin Morgan 2023-05-25 15:39:31 -05:00
parent ed8a4458c9
commit 4b61cb9381
Signed by: daylin
GPG key ID: C1E52E7DD81DF79F

View file

@ -21,6 +21,8 @@ import sys
import tempfile import tempfile
import threading import threading
import time import time
from urllib.request import urlopen
from urllib.error import HTTPError
import venv import venv
from argparse import SUPPRESS, Action from argparse import SUPPRESS, Action
from argparse import ArgumentParser as StdArgParser from argparse import ArgumentParser as StdArgParser
@ -49,7 +51,7 @@ from typing import (
Generator, Generator,
) )
__version__ = "22.12a3-41-g5c40210-dev" __version__ = "22.12a3-43-g0e9779a-dev"
@dataclass @dataclass
@ -305,6 +307,16 @@ def echo(
fd.write(output) fd.write(output)
def confirm(question: str) -> bool:
while True:
ans = input(question + a.style(" (Y)es/(n)o: ", "yellow")).strip().lower()
if ans in ("y", "yes"):
return True
elif ans in ("n", "no"):
return False
sys.stdout.write("\nPlease select (Y)es or (n)o.")
def run( def run(
command: List[str], command: List[str],
spinmsg: str = "", spinmsg: str = "",
@ -448,7 +460,7 @@ class ViVenv:
a.table((("key", "value"), *((k, v) for k, v in info.items()))) a.table((("key", "value"), *((k, v) for k, v in info.items())))
def use(*packages: str, track_exe: bool = False, name: str = "") -> None: def use(*packages: str, track_exe: bool = False, name: str = "") -> ViVenv:
"""create a vivenv and append to sys.path """create a vivenv and append to sys.path
Args: Args:
@ -467,6 +479,7 @@ def use(*packages: str, track_exe: bool = False, name: str = "") -> None:
vivenv.dump_info(write=True) vivenv.dump_info(write=True)
modify_sys_path(vivenv.path) modify_sys_path(vivenv.path)
return vivenv
def validate_spec(spec: Tuple[str, ...]) -> None: def validate_spec(spec: Tuple[str, ...]) -> None:
@ -515,8 +528,7 @@ STANDALONE_TEMPLATE = r"""
# >>>>> code golfed with <3 # >>>>> code golfed with <3
""" # noqa """ # noqa
STANDALONE_TEMPLATE_USE = r""" STANDALONE_TEMPLATE_USE = r"""def _viv_use(*pkgs: str, track_exe: bool = False, name: str = "") -> None:
def _viv_use(*pkgs: str, track_exe: bool = False, name: str = "") -> None:
i,s,m,e,spec=__import__,str,map,lambda x: True if x else False,[*pkgs] i,s,m,e,spec=__import__,str,map,lambda x: True if x else False,[*pkgs]
if not {{*m(type,pkgs)}}=={{s}}: raise ValueError(f"spec: {{pkgs}} is invalid") if not {{*m(type,pkgs)}}=={{s}}: raise ValueError(f"spec: {{pkgs}} is invalid")
ge,sys,P,ew=i("os").getenv,i("sys"),i("pathlib").Path,i("sys").stderr.write ge,sys,P,ew=i("os").getenv,i("sys"),i("pathlib").Path,i("sys").stderr.write
@ -535,9 +547,13 @@ def _viv_use(*pkgs: str, track_exe: bool = False, name: str = "") -> None:
i("json").dump({{"created":s(i("datetime").datetime.today()),"id":_id,"spec":spec,"exe":exe}},f) i("json").dump({{"created":s(i("datetime").datetime.today()),"id":_id,"spec":spec,"exe":exe}},f)
sys.path = [p for p in (*sys.path,s(*(env/"lib").glob("py*/si*"))) if p!=i("site").USER_SITE] sys.path = [p for p in (*sys.path,s(*(env/"lib").glob("py*/si*"))) if p!=i("site").USER_SITE]
_viv_use({spec}) _viv_use({spec})
"""[ # noqa """ # noqa
1:
] SHOW_TEMPLATE = f"""
{a.style('Version', 'bold')}: {{version}}
{a.style('CLI', 'bold')}: {{cli}}
{a.style('Current Source', 'bold')}: {{src}}
"""
def noqa(txt: str) -> str: def noqa(txt: str) -> str:
@ -691,7 +707,7 @@ class CustomHelpFormatter(RawDescriptionHelpFormatter, HelpFormatter):
# determine the required width and the entry label # determine the required width and the entry label
help_position = min(self._action_max_length + 2, self._max_help_position) help_position = min(self._action_max_length + 2, self._max_help_position)
help_width = max(self._width - help_position, 11) help_width = max(self._width - help_position, 11)
action_width = help_position - self._current_indent - 2 action_width = help_position - self._current_indent
action_header = self._format_action_invocation(action) action_header = self._format_action_invocation(action)
action_header_len = len(a.escape(action_header)) action_header_len = len(a.escape(action_header))
@ -763,7 +779,8 @@ class ArgumentParser(StdArgParser):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.formatter_class = lambda prog: CustomHelpFormatter( self.formatter_class = lambda prog: CustomHelpFormatter(
prog, max_help_position=35 prog,
max_help_position=35,
) )
def error(self, message: str) -> NoReturn: def error(self, message: str) -> NoReturn:
@ -788,6 +805,12 @@ within python script:
class Viv: class Viv:
def __init__(self) -> None: def __init__(self) -> None:
self.vivenvs = get_venvs() self.vivenvs = get_venvs()
self.current_source = Path(__file__).resolve()
self.name = (
"python3 <(curl -fsSL gh.dayl.in/viv/viv.py)"
if str(self.current_source).startswith("/proc/")
else "viv"
)
def _match_vivenv(self, name_id: str) -> ViVenv: # type: ignore[return] def _match_vivenv(self, name_id: str) -> ViVenv: # type: ignore[return]
# TODO: improve matching algorithm to favor names over id's # TODO: improve matching algorithm to favor names over id's
@ -895,11 +918,81 @@ class Viv:
vivenv.dump_info() vivenv.dump_info()
def manage(self, args: Namespace) -> None:
"""manage viv installation"""
if args.cmd == "show":
# NOTE: could reuse the table output for this?
echo("Current:")
sys.stdout.write(
SHOW_TEMPLATE.format(
version=__version__,
cli=shutil.which("viv"),
src=Path(__file__).resolve(),
)
)
elif args.cmd == "update":
if str(Path(__file__).resolve()).startswith("/proc/"):
error(
a.style("viv manage update", "bold")
+ " should only be used with a locally installed viv",
1,
)
try:
r = urlopen(
"https://raw.githubusercontent.com/daylinmorgan/viv/"
+ args.reference
+ "/src/viv/viv.py"
)
except HTTPError as e:
error(
"Issue updating viv see below:"
+ a.style("-> ", "red").join(["\n"] + repr(e).splitlines())
)
if "404" in repr(e):
echo("Please check your reference is valid.", style="red")
sys.exit(1)
viv_src = r.read()
(hash := hashlib.sha256()).update(viv_src)
sha256 = hash.hexdigest()
(
src_cache := Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"))
/ "viv"
/ "src"
).mkdir(exist_ok=True, parents=True)
cached_version = src_cache / f"{sha256}.py"
if not cached_version.is_file():
with cached_version.open("w") as f:
f.write(viv_src.decode())
sys.path.append(str(src_cache))
next_version = __import__(sha256).__version__
q = (
"Update source at: "
+ a.style(Path(__file__).resolve(), "bold")
+ f" \n from version {__version__} to {next_version}?"
)
if confirm(q):
print("AWAY THEN")
elif args.cmd == "install":
echo("not yet implemented. sorry")
def _get_subcmd_parser( def _get_subcmd_parser(
self, subparsers: _SubParsersAction[ArgumentParser], name: str, **kwargs: Any self,
subparsers: _SubParsersAction[ArgumentParser],
name: str,
attr: Optional[str] = None,
**kwargs: Any,
) -> ArgumentParser: ) -> ArgumentParser:
aliases = kwargs.pop("aliases", [name[0]]) aliases = kwargs.pop("aliases", [name[0]])
cmd = getattr(self, name) cmd = getattr(self, attr if attr else name)
parser: ArgumentParser = subparsers.add_parser( parser: ArgumentParser = subparsers.add_parser(
name, name,
help=cmd.__doc__.splitlines()[0], help=cmd.__doc__.splitlines()[0],
@ -914,7 +1007,7 @@ class Viv:
def cli(self) -> None: def cli(self) -> None:
"""cli entrypoint""" """cli entrypoint"""
parser = ArgumentParser(description=description) parser = ArgumentParser(prog=self.name, description=description)
parser.add_argument( parser.add_argument(
"-V", "-V",
"--version", "--version",
@ -927,10 +1020,7 @@ class Viv:
) )
p_vivenv_arg = ArgumentParser(add_help=False) p_vivenv_arg = ArgumentParser(add_help=False)
p_vivenv_arg.add_argument("vivenv", help="name/hash of vivenv") p_vivenv_arg.add_argument("vivenv", help="name/hash of vivenv")
p_list = self._get_subcmd_parser( p_list = self._get_subcmd_parser(subparsers, "list")
subparsers,
"list",
)
p_list.add_argument( p_list.add_argument(
"-q", "-q",
@ -955,16 +1045,15 @@ class Viv:
nargs="*", nargs="*",
) )
p_exe_python = p_exe_sub.add_parser( p_exe_sub.add_parser(
"python", "python",
help="run command with python", help="run command with python",
parents=[p_vivenv_arg, p_exe_shared], parents=[p_vivenv_arg, p_exe_shared],
) ).set_defaults(func=self.exe, exe="python")
p_exe_pip = p_exe_sub.add_parser(
p_exe_sub.add_parser(
"pip", help="run command with pip", parents=[p_vivenv_arg, p_exe_shared] "pip", help="run command with pip", parents=[p_vivenv_arg, p_exe_shared]
) ).set_defaults(func=self.exe, exe="pip")
p_exe_python.set_defaults(func=self.exe, exe="python")
p_exe_pip.set_defaults(func=self.exe, exe="pip")
p_remove = self._get_subcmd_parser( p_remove = self._get_subcmd_parser(
subparsers, subparsers,
@ -1009,6 +1098,31 @@ class Viv:
parents=[p_vivenv_arg], parents=[p_vivenv_arg],
) )
p_manage_sub = self._get_subcmd_parser(
subparsers, name="manage"
).add_subparsers(title="subcommand", metavar="<sub-cmd>", required=True)
p_manage_sub.add_parser(
"install", help="install viv", aliases="i"
).set_defaults(func=self.manage, cmd="install")
(
p_manage_update := p_manage_sub.add_parser(
"update", help="update viv version", aliases="u"
)
).set_defaults(func=self.manage, cmd="update")
p_manage_update.add_argument(
"-r",
"--reference",
help="git reference (branch/tag/commit)",
default="main",
)
p_manage_sub.add_parser(
"show", help="show current installation info", aliases="s"
).set_defaults(func=self.manage, cmd="show")
args = parser.parse_args() args = parser.parse_args()
args.func(args) args.func(args)