mirror of
https://github.com/daylinmorgan/viv.git
synced 2024-12-22 02:30:44 -06:00
refactor: abstract _uses_viv + add tests
This commit is contained in:
parent
e31d401585
commit
5dacf8ab60
2 changed files with 90 additions and 36 deletions
|
@ -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.use)
|
(?:viv\.)
|
||||||
""",
|
)
|
||||||
txt,
|
(?P<mode>(\w+))
|
||||||
re.VERBOSE | re.MULTILINE,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _uses_viv_run(txt: str) -> bool:
|
|
||||||
return bool(
|
|
||||||
re.search(
|
|
||||||
r"""
|
|
||||||
^(?!\#)\s*
|
|
||||||
(?:__import__\(\s*["']viv["']\s*\).run)
|
|
||||||
|
|
|
||||||
(?:from\ viv\ import\ run)
|
|
||||||
|
|
|
||||||
(?:viv.run)
|
|
||||||
""",
|
""",
|
||||||
txt,
|
txt,
|
||||||
re.VERBOSE | re.MULTILINE,
|
re.VERBOSE | re.MULTILINE,
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
if len(matches) == 0:
|
||||||
|
return _Viv_Mode.NONE
|
||||||
|
elif len(matches) > 1:
|
||||||
|
err_quit(
|
||||||
|
"Unexpected number of viv references in script.\n"
|
||||||
|
"Expected only 1, found: "
|
||||||
|
+ ", ".join((a.style(match, "bold") for match in matches))
|
||||||
)
|
)
|
||||||
|
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
52
tests/test_readers.py
Normal 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
|
Loading…
Reference in a new issue