refactor: abstract _uses_viv + add tests

This commit is contained in:
Daylin Morgan 2024-01-04 15:53:04 -06:00
parent e31d401585
commit 5dacf8ab60
Signed by: daylin
GPG key ID: C1E52E7DD81DF79F
2 changed files with 90 additions and 36 deletions

View file

@ -33,6 +33,7 @@ from argparse import (
from argparse import ArgumentParser as StdArgParser from argparse import ArgumentParser as StdArgParser
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime from datetime import datetime
from enum import Enum
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from pathlib import Path from pathlib import Path
from textwrap import dedent, fill from textwrap import dedent, fill
@ -2864,41 +2865,43 @@ def make_executable(path: Path) -> None:
os.chmod(path, mode) os.chmod(path, mode)
# TODO: abstract/deduplicate these functions class _Viv_Mode(Enum):
def _uses_viv_use(txt: str) -> bool: NONE = 0
return bool( USE = 1
re.search( RUN = 2
def _uses_viv(txt: str) -> _Viv_Mode:
matches = [
match.group("mode")
for match in re.finditer(
r""" r"""
^(?!\#)\s* ^(?!\#)\s* # ignore comments/shebangs
(?:__import__\(\s*["']viv["']\s*\).use) (
| (?:__import__\(\s*["']viv["']\s*\)\.)
(?:from\ viv\ import\ use) |
#| (?:from\s+viv\s+import\s+)
#(?:import\ viv) |
| (?:viv\.)
(?:viv.use) )
""", (?P<mode>(\w+))
""",
txt, txt,
re.VERBOSE | re.MULTILINE, re.VERBOSE | re.MULTILINE,
) )
) ]
if len(matches) == 0:
return _Viv_Mode.NONE
def _uses_viv_run(txt: str) -> bool: elif len(matches) > 1:
return bool( err_quit(
re.search( "Unexpected number of viv references in script.\n"
r""" "Expected only 1, found: "
^(?!\#)\s* + ", ".join((a.style(match, "bold") for match in matches))
(?:__import__\(\s*["']viv["']\s*\).run)
|
(?:from\ viv\ import\ run)
|
(?:viv.run)
""",
txt,
re.VERBOSE | re.MULTILINE,
) )
) elif (match := matches[0]) in {"run", "use"}:
return _Viv_Mode[match.upper()]
else:
err_quit(f"Unknown function {a.bold}{matches[0]}{a.end} associated with viv.")
METADATA_BLOCK = ( METADATA_BLOCK = (
@ -3437,21 +3440,20 @@ class Viv:
script_text = fetch_script(script) script_text = fetch_script(script)
scriptpath.write_text(script_text) scriptpath.write_text(script_text)
has_use = _uses_viv_use(script_text) mode = _uses_viv(script_text)
has_run = _uses_viv_run(script_text)
metadata = _read_metadata_block(script_text) metadata = _read_metadata_block(script_text)
deps = metadata.get("dependencies", []) deps = metadata.get("dependencies", [])
if requires := metadata.get("requires-python", ""): if requires := metadata.get("requires-python", ""):
_check_python(requires) _check_python(requires)
if has_use and deps: if mode == _Viv_Mode.USE and deps:
error( error(
"Script Dependencies block and " "Script Dependencies block and "
"`viv.use` API can't be used in the same script" "`viv.use` API can't be used in the same script"
) )
if not self.local_source and (has_use or has_run): if not self.local_source and mode != _Viv_Mode.NONE:
log.debug("fetching remote copy to use for python api") log.debug("fetching remote copy to use for python api")
(tmppath / "viv.py").write_text( (tmppath / "viv.py").write_text(
fetch_script( fetch_script(
@ -3461,7 +3463,7 @@ class Viv:
self._update_cache(env, keep, tmpdir) self._update_cache(env, keep, tmpdir)
if has_use: if mode == _Viv_Mode.USE:
log.debug(f"script invokes viv.use passing along spec: \n '{spec}'") log.debug(f"script invokes viv.use passing along spec: \n '{spec}'")
subprocess_run_quit( subprocess_run_quit(
[sys.executable, "-S", scriptpath, *rest], [sys.executable, "-S", scriptpath, *rest],
@ -3471,7 +3473,7 @@ class Viv:
PYTHONPATH=":".join((str(tmppath), env.get("PYTHONPATH", ""))), PYTHONPATH=":".join((str(tmppath), env.get("PYTHONPATH", ""))),
), ),
) )
elif has_run: elif mode == _Viv_Mode.RUN:
log.debug("script invokes viv.run letting subprocess handle deps") log.debug("script invokes viv.run letting subprocess handle deps")
subprocess_run_quit( subprocess_run_quit(
[sys.executable, "-S", scriptpath, *rest], [sys.executable, "-S", scriptpath, *rest],

52
tests/test_readers.py Normal file
View file

@ -0,0 +1,52 @@
import pytest
from viv.viv import _read_metadata_block, _uses_viv, _Viv_Mode
RUN_METADATA_SCRIPT = """
#!/usr/bin/env -S viv run -s
# /// script
# requires-python = ">3.10"
# dependencies = [
# "rich"
# ]
# ///
"""
USE_SCRIPT = """
#!/usr/bin/env python3
__import__("viv").use("rich")
from rich import print
print("pretty!")
"""
def test_metadata():
metadata = _read_metadata_block(RUN_METADATA_SCRIPT)
assert metadata == {"requires-python": ">3.10", "dependencies": ["rich"]}
def test_uses():
assert _uses_viv(RUN_METADATA_SCRIPT) == _Viv_Mode.NONE
assert (
_uses_viv(RUN_METADATA_SCRIPT + """\n__import__("viv").run()\n""")
== _Viv_Mode.RUN
)
assert _uses_viv(USE_SCRIPT) == _Viv_Mode.USE
assert _uses_viv("# from viv import use") == _Viv_Mode.NONE
def test_uses_fail(caplog):
with pytest.raises(SystemExit):
_uses_viv("""__import__("viv").run()\n__import__("viv").use()""")
with pytest.raises(SystemExit):
_uses_viv("""__import__("viv").unknown()""")
assert [
(
"viv",
40,
"Unexpected number of viv references in script.\n"
"Expected only 1, found: run, use",
),
("viv", 40, "Unknown function unknown associated with viv."),
] == caplog.record_tuples