diff --git a/src/viv/__init__.py b/src/viv/__init__.py index 6a86724..da6f511 100644 --- a/src/viv/__init__.py +++ b/src/viv/__init__.py @@ -1 +1 @@ -from .viv import use # noqa +from .viv import use, __version__ # noqa diff --git a/src/viv/viv.py b/src/viv/viv.py index 44886c3..6b61f52 100755 --- a/src/viv/viv.py +++ b/src/viv/viv.py @@ -51,7 +51,7 @@ from typing import ( Generator, ) -__version__ = "22.12a3-56-ga9d0098-dev" +__version__ = "22.12a3-56-g4566e2e-dev" @dataclass @@ -64,6 +64,11 @@ class Config: srccache: Path = ( Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache")) / "viv" / "src" ) + srcdefault: Path = ( + Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share")) + / "viv" + / "viv.py" + ) def __post_init__(self) -> None: self.venvcache.mkdir(parents=True, exist_ok=True) @@ -71,6 +76,7 @@ class Config: parents=True, exist_ok=True, ) + self.srcdefault.parent.mkdir(parents=True, exist_ok=True) c = Config() @@ -244,6 +250,9 @@ class Ansi: else: return (row,) + def viv_preamble(self, style: str = "magenta", sep: str = "::") -> str: + return f"{self.cyan}Viv{self.end}{self.__dict__[style]}{sep}{self.end}" + def table( self, rows: Tuple[Tuple[str, Sequence[str]], ...], header_style: str = "cyan" ) -> None: @@ -308,20 +317,25 @@ def echo( msg: str, style: str = "magenta", newline: bool = True, fd: TextIO = sys.stderr ) -> None: """output general message to stdout""" - output = f"{a.cyan}Viv{a.end}{a.__dict__[style]}::{a.end} {msg}" + output = f"{a.viv_preamble(style)} {msg}" if newline: output += "\n" fd.write(output) -def confirm(question: str) -> bool: +def confirm(question: str, context: str = "") -> bool: + sys.stderr.write(context) + sys.stderr.write( + a.viv_preamble(sep="?? ") + question + a.style(" (Y)es/(n)o: ", "yellow") + ) while True: - ans = input(question + a.style(" (Y)es/(n)o: ", "yellow")).strip().lower() + ans = input().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.") + sys.stdout.write("Please select (Y)es or (n)o. ") + sys.stdout.write("\n") def run( @@ -842,6 +856,13 @@ def fetch_source(reference: str) -> str: return sha256 +def make_executable(path: Path) -> None: + """apply an executable bit for all users with read access""" + mode = os.stat(path).st_mode + mode |= (mode & 0o444) >> 2 # copy R bits to X + os.chmod(path, mode) + + class Viv: def __init__(self) -> None: self.vivenvs = get_venvs() @@ -855,6 +876,7 @@ class Viv: self.local = not str(self.running_source).startswith("/proc/") if self.local: self.local_source = self.running_source + self.local_version = __version__ else: try: _local_viv = __import__("viv") @@ -973,6 +995,24 @@ class Viv: vivenv.dump_info() + def _install_local_src(self, sha256: str, src: Path, cli: Path) -> None: + echo("updating local source copy of viv") + shutil.copy(c.srccache / f"{sha256}.py", src) + make_executable(src) + echo("symlinking cli") + + if not cli.is_file(): + cli.symlink_to(src) + else: + cli.unlink() + cli.symlink_to(src) + + echo("Remember to include the following line in your shell rc file:") + sys.stderr.write( + ' `export PYTHONPATH="$PYTHONPATH:$HOME/' + f'{src.relative_to(Path.home())}"\n' + ) + def manage(self, args: Namespace) -> None: """manage viv itself""" @@ -988,7 +1028,7 @@ class Viv: ) elif args.cmd == "update": - if not self.local_source: + if self.local_source == "Not Found": error( a.style("viv manage update", "bold") + " should be used with an exisiting installation", @@ -1012,12 +1052,14 @@ class Viv: ) if confirm(q): - echo(f"updating local source copy of viv at {self.local_source}") - shutil.copy(c.srccache / f"{sha256}.py", self.local_source) - # TODO: check/update the cli + self._install_local_src( + sha256, + args.cli if self.local_source == "Not Found" else args.local_source, + args.cli, + ) elif args.cmd == "install": - if not self.local_source == "NotFound": + if not self.local_source == "Not Found": error(f"found existing viv installation at {self.local_source}") echo( "use " @@ -1032,14 +1074,17 @@ class Viv: downloaded_version = __import__(sha256).__version__ echo(f"Downloaded version: {downloaded_version}") - # TODO: src path and cli path should be shard args between update/install + # TODO: see if file is actually where + # we are about to install and give instructions - q = INSTALL_TEMPLATE.format( - src_location=args.src, cli_location=args.cli - ) + ("Would you like to perform the above " "installation steps?") - - if confirm(q): - print("AWAY MAN") + if confirm( + "Would you like to perform the above installation steps?", + INSTALL_TEMPLATE.format( + src_location=args.src, + cli_location=args.cli, + ), + ): + self._install_local_src(sha256, args.src, args.cli) def _get_subcmd_parser( self, @@ -1166,13 +1211,13 @@ class Viv: "-s", "--src", help="path/to/source_file", - default=c.srccache / "viv.py", + default=c.srcdefault, ) p_manage_shared.add_argument( "-c", "--cli", help="path/to/cli (symlink to src)", - default=Path.home() / "bin" / "viv.py", + default=Path.home() / "bin" / "viv", ) p_manage_sub = self._get_subcmd_parser(