Skip to content

Commit

Permalink
fix: more dependencies; docs
Browse files Browse the repository at this point in the history
  • Loading branch information
robinvandernoord committed Mar 4, 2024
1 parent fbe15a5 commit dc0ef76
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 8 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ uv install uvx
pipx install uvx
```

## Usage
```bash
uvx
```

Run `uvx` without any arguments to see all possible subcommands.

## License

`usvx` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
`uvx` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.

## Changelog

Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ dependencies = [
"typer",
"plumbum",
"threadful>=0.2",
"rich",
"quickle",
"packaging",
]

[project.optional-dependencies]
Expand Down
7 changes: 5 additions & 2 deletions src/uvx/_python.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import sys
import textwrap
from pathlib import Path

import plumbum # type: ignore
from plumbum.cmd import grep, uv # type: ignore
from plumbum.cmd import grep # type: ignore

_uv = plumbum.local[sys.executable]["-m", "uv"]


def _run_python_in_venv(*args: str, venv: Path) -> str:
Expand Down Expand Up @@ -41,4 +44,4 @@ def get_package_version(package: str) -> str:
"""Get the currently installed version of a specific package."""
# assumes `with virtualenv(venv)` block executing this function
# uv pip freeze | grep ^su6==
return (uv["pip", "freeze"] | grep[f"^{package}=="])().strip().split("==")[-1]
return (_uv["pip", "freeze"] | grep[f"^{package}=="])().strip().split("==")[-1]
75 changes: 75 additions & 0 deletions src/uvx/cli.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
"""This file builds the Typer cli."""

import os
import subprocess # nosec
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional

import plumbum # type: ignore
import rich
import typer
from typer import Context

from uvx._constants import BIN_DIR

from .__about__ import __version__
from .core import (
as_virtualenv,
format_bools,
Expand All @@ -25,6 +32,7 @@
@app.command()
def install(package_name: str, force: bool = False, python: str = ""):
"""Install a package (by pip name)."""
# todo: support 'install .'
install_package(package_name, python=python, force=force)


Expand Down Expand Up @@ -132,3 +140,70 @@ def runpython(venv: str, ctx: Context):
# version or --version (incl. 'uv' version and Python version)

# ...


def add_to_bashrc(text: str, with_comment: bool = True):
"""Add text to ~/.bashrc, usually with a comment (uvx + timestamp)."""
with (Path.home() / ".bashrc").resolve().open("a") as f:
now = str(datetime.now()).split(".")[0]
final_text = "\n"
final_text += f"# Added by `uvx` at {now}\n" if with_comment else ""
final_text += text + "\n"
f.write(final_text)


@app.command()
def ensurepath(force: bool = False):
"""Update ~/.bashrc with a PATH that includes the local bin directory that uvx uses."""
env_path = os.getenv("PATH", "")
bin_in_path = str(BIN_DIR) in env_path.split(":")

if bin_in_path and not force:
rich.print(
f"[yellow]{BIN_DIR} is already added to your path. Use '--force' to add it to your .bashrc file anyway.[/yellow]"
)
exit(1)

add_to_bashrc(f'export PATH="$PATH:{BIN_DIR}"')


@app.command()
def completions(): # noqa
"""
Use --install-completion to install the autocomplete script, \
or --show-completion to see what would be installed.
"""
rich.print("Use 'uvx --install-completion' to install the autocomplete script to your '.bashrc' file.")


def version_callback():
"""Show the current versions when running with --version."""
rich.print("uvx", __version__)
run_command("uv", "--version", printfn=rich.print)
rich.print("Python", sys.version.split(" ")[0])


@app.callback(invoke_without_command=True, no_args_is_help=True)
def main(
ctx: typer.Context,
# stops the program:
version: bool = False,
) -> None: # noqa
"""
This callback will run before every command, setting the right global flags.
Args:
ctx: context to determine if a subcommand is passed, etc
version: display current version?
"""
if version:
version_callback()
elif not ctx.invoked_subcommand:
rich.print("[yellow]Missing subcommand. Try `uvx --help` for more info.[/yellow]")
# else: just continue


if __name__ == "__main__": # pragma: no cover
app()
11 changes: 6 additions & 5 deletions src/uvx/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
import plumbum # type: ignore
import rich
from plumbum import local # type: ignore
from plumbum.cmd import uv as _uv # type: ignore
from threadful import thread
from threadful.bonus import animate

from ._constants import WORK_DIR
from ._python import get_package_version, get_python_executable, get_python_version
from ._python import _uv, get_package_version, get_python_executable, get_python_version
from ._symlinks import find_symlinks, install_symlinks, remove_symlink
from .metadata import Metadata, collect_metadata, read_metadata, store_metadata

Expand Down Expand Up @@ -283,11 +282,13 @@ def list_packages() -> typing.Generator[tuple[str, Metadata | None], None, None]
yield subdir.name, metadata


def run_command(command: str, *args: str):
def run_command(command: str, *args: str, printfn: typing.Callable[..., None] = print):
"""Run a command via plumbum without raising an error on exception."""
# retcode = None makes the command not raise an exception on error:
exit_code, stdout, stderr = plumbum.local[command][args].run(retcode=None)

print(stdout)
print(stderr, file=sys.stderr)
if stdout:
printfn(stdout.strip())
if stderr:
printfn(stderr.strip(), file=sys.stderr)
return exit_code

0 comments on commit dc0ef76

Please sign in to comment.