diff --git a/docs/conf.py b/docs/conf.py index a7fb6d1..a53b411 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,6 +3,7 @@ copyright = "2023, Daylin Morgan" author = "Daylin Morgan" extensions = ["myst_parser", "sphinx_copybutton"] +myst_enable_extensions = ["colon_fence", "deflist"] templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] @@ -13,4 +14,10 @@ html_theme = "shibuya" # html_static_path = ["_static"] html_logo = "../assets/logo.svg" -html_theme_options = {"github_url": "https://github.com/daylinmorgan/viv"} +html_theme_options = { + "github_url": "https://github.com/daylinmorgan/viv", + "nav_links": [ + {"title": "Documentation", "url": "installation"}, + {"title": "CLI Reference", "url": "cli"}, + ], +} diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..cda7a01 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,45 @@ +# Configuration + +## Environment Variables + +`VIV_RUN_MODE` +: **ephemeral** (default): + : `viv run` will generate a temporary directory that is removed following execution +: **semi-ephemeral** + : `viv run` will set the `VIV_CACHE` the directory to `$TEMPDIR/viv-ephemeral-cache-$USER` +: persist + : `viv run` will always use the standard `VIV_CACHE` which maximizes reusable vivenvs + +`VIV_CACHE` +: Path to use for vivenv cache by default `$XDG_CACHE_HOME/viv` or `$HOME/.cache/viv` + +`VIV_LOG_PATH` +: Path to use log file by default `$XDG_DATA_HOME/viv/viv.log` or `$HOME/.local/share/viv/viv.log` + +`VIV_BIN_DIR` +: Path to use for shims by default `$HOME/.local/bin` + +`VIV_NO_SETUPTOOLS` +: Don't add setuptools to generated vivenvs. +: Many legacy packages expect setuptools to be available + and don't appropriate declare it as dependency. + To minimize frustration `setuptools` is added to every dependency + list. + +`VIV_FORCE` +: Remove existence check and recreate vivenv + +`VIV_SPEC` +: Space separated list of dependencies in addition to those in script + +`VIV_VERBOSE` +: Show `pip` output in real time + +`VIV_DEBUG` +: Set log level to `DEBUG` + +`FORCE_COLOR` +: Force output to use ANSI escape codes + +`NO_COLOR` +: Remove all ANSI escape codes from output diff --git a/docs/index.md b/docs/index.md index e63222c..5e6b5df 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,7 @@ --- -hide-toc: true +layout: landing +cover: http://viv.dayl.in/_static/logo.svg +desciption: Viv isn't venv --- # viv isn't venv @@ -8,24 +10,32 @@ Try before you buy! ```sh python3 <(curl -fsSL viv.dayl.in/viv.py) run pycowsay -- "viv isn't venv\!" ``` ---- -`Viv` is a standalone dependency-free `venv` creator [^1]. +:::{container} buttons + + [Docs](/installation) + [Github](https://github.com/daylinmorgan/viv) + +::: + + +`Viv` is a standalone dependency-free `venv` creator (just needs python + pip). `Viv` helps you ignore silly things like managing temporary or rarely used virtual environments, while still unleashing the full power of python scripting with it's entire ecosystem at your disposal. -`Viv`'s uncompromising insistence on portability means that it will always: - -1. only use the standard library -2. never exceed a single script. +`Viv`'s uncompromising insistence on portability means that it will always, +only use the standard library and never exceed a single script. Documentation is currently a WIP please see the [cli reference](./cli.md) and the [README](https://github.com/daylinmorgan/viv) ```{toctree} :hidden: +:maxdepth: 2 +installation.md usage.md +configuration.md +vs-others.md cli.md ``` - diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..dac489e --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,56 @@ +# Installation + +## Automated + +Run the below command to install `viv`. + +```sh +python3 <(curl -fsSL viv.dayl.in/viv.py) manage install +``` + +To access `viv` from within scripts you should add its location to your `PYTHONPATH`. +By default `viv` will be installed to `$XDG_DATA_HOME/viv` or `~/.local/share/viv`, +and symlinked to `$XDG_BIN_HOME` or `~/.local/bin/.` +You can customize these locations at install with with `--src` and `--cli` respectively. + +```sh +export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/viv" +# or +export PYTHONPATH="$PYTHONPATH:$(viv manage show --pythonpath)" +``` + +:::{note} +You can install an older version by specifying `--ref` +::: + +## Manual + +Viv is a single standalone script meaning all that's really necessary is +that it exists locally and is appropriately added to your path. +You can get the latest stable version from [here](https://viv.dayl.in/viv.py) +or from [github](https://github.com/daylinmorgan/viv/blob/latest/src/viv/viv.py). + +Must be added to your `$PATH` for use as a CLI app and `$PYTHONPATH` for uses as a python module. + +## Never + +Viv is a standalone script accessible at `https://viv.dayl.in/viv.py` +meaning it's not strictly necessary you every actually install it. +Every instance of the `viv` CLI can be seamlessly replaced with `python3 <(curl -fsSL viv.dayl.in/viv.py)`. + +## PyPI Options (Not Recommended) + +Why is this *not recommended?* +Mainly because `viv` is all about hacking your `sys.path`. +Placing it in its own virtual environment +or installing in a user site directory may complicate this endeavor. + +```sh +pip install viv +# or +pipx install viv +``` + +*Note*: If installed by `pipx`, it should still be possible to +use `viv` as a module by adding it to the `$PYTHONPATH`. +See above for more info. diff --git a/docs/usage.md b/docs/usage.md index 5933a1a..facfcb0 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,12 +1,181 @@ # Usage -In any python script with external dependencies you can add this line, +Viv works by ensuring scripts or a given +command are run in an appropriate environment with all specified dependencies. + +```{tip} +`viv` is a single script and available at viv.dayl.in/viv.py +meaning every instance of `viv` in these examples could be `python3 <(curl -fsSL viv.dayl.in/viv.py)` +``` + +## Run CLI Apps + +Run a python app that provides an entrypoint and separate args with `--`: + +```sh +viv run frogmouth -- gh daylinmorgan/viv +``` + +To run a python module use the `-b/--bin` flag and specify `python`: + +```sh +viv run rich -b python -- -m rich +``` + +Make an ephemeral `jupyter` environment with your needed deps: + +```sh +viv run jupyter pandas -r requirements-dev.txt -- notebook +``` + +Generate an executable shell script that will on demand create a vivenv as needed: + +```sh +viv shim ruff +``` + +Output: +```python +#!/usr/bin/env python3 +# AUTOGENERATED by viv (v2023.1003) +# see `python3 <(curl -fsSL viv.dayl.in/viv.py) --help` + +import subprocess +import sys + +if __name__ == "__main__": + vivenv = __import__("viv").use("ruff") # noqa + sys.exit(subprocess.run([vivenv / "bin" / "ruff", *sys.argv[1:]]).returncode) +``` + +## Run Python Scripts + +It's possible to use the `viv` CLI to run a python script. +There are several options for invoking a script. + +Using the interpreter: +```sh +viv run rich typer -b python -- ./cli.py --help +``` + +Using `-s/--script`: +```sh +viv run rich typer -s ./cli.py -- --help +# or with a remote script +viv run rich -s https://raw.githubusercontent.com/Textualize/rich/master/examples/fullscreen.py +``` + +If `viv` is available on your path it's possible to +invoke it with embedded metadata thanks to shebangs: + +```python +#!/usr/bin/env -S viv run --keep --script +# /// script +# requires-python = ">3.10" +# dependencies = [ +# "matplotlib", +# "pandas" +# ] +# /// +``` + +```{note} +If using a shebang on a python script `-s/--script` must be the last argument +``` + +```{seealso} +Check out [PEP723](https://peps.python.org/pep-0723/) for more info about inline script metadata. +``` + +In any python script with external dependencies you can also add this line prior to imports to automate `vivenv` creation and installation of dependencies. ```python __import__("viv").use("click") ``` +If your dependencies are sensitive to the version of python +(numpy, cpython-based apps, etc. ) then you can specify `track_exe`. +Which will lead to `viv` creating a unique vivenv based on the detected python executable. + +```python +__import__("viv").use("numpy", track_exe=True) +``` + +If you'd like to pin your dependencies to a resolved environment you +can use the convenience command `viv freeze` to output a list of pinned packages. + +Command: +```sh +viv freeze rich typer +``` + +Output: +```python +__import__("viv").use("rich==13.7.0", "typer==0.9.0", "click==8.1.7", "markdown-it-py==3.0.0", "Pygments==2.17.2", "typing_extensions==4.9.0", "mdurl==0.1.2") # noqa +``` + +Additionally, you can make this work regardless of `PYTHONPATH` by using `--path`. + +Command: +```sh +viv freeze rich typer --path rel +``` + +Output: +```python +__import__("sys").path.append(__import__("os").path.expanduser("~/.local/share/viv/")) # noqa +__import__("viv").use("rich==13.7.0", "typer==0.9.0", "click==8.1.7", "markdown-it-py==3.0.0", "Pygments==2.17.2", "typing_extensions==4.9.0", "mdurl==0.1.2") # noqa +``` + +## Manage Viv + +### Interacting with the viv cache + +Depending on how you invoke `viv`, it will persist it's virtual environments (vivenvs). +To see all currently existing vivenvs use can use `viv list`. + +Viv will attempt to track any usages of the +vivenvs including the scripts that invoke them. + +You can remove any existing vivenvs using `viv env remove`: + +```sh +viv env remove d4b342b3 +``` + +To get more information about vivenvs you can use `viv list --verbose` or `viv env info ` + +```{note} +For commands that expect a vivenv hash/name you can use as few characters as you +as you like and `viv` will match it against the existing vivenvs in the cache. +``` + +You can list vivenvs given a criteria using `--filter` for example: + +Created before 2024-01-01: +```sh +viv list --filter "created-before:2024-01-01" +``` + +Associated with a particular file: +```sh +viv list --filter "files:./script.py" +``` + +Or no files: +```sh +viv list --filter "files:None" +``` + +```{note} +`--filter "files:None"` will also apply to vivenvs +in which the original file is no longer on the disk +``` + +The available filtering criteria are `accessed-after`, +`accessed-before`, `created-before`, `created-after`, and `files`. + To remove all `vivenvs` you can use the below command: ```sh @@ -18,35 +187,8 @@ To remove `viv` all together you can use the included `purge` command: ```sh python3 <(curl -fsSL viv.dayl.in/viv.py) manage purge ``` -## Equivalent commands from alternatives -### [pip-run](https://github.com/jaraco/pip-run) - - -```sh -pip-run cowsay -- -m cowsay "moove over, pip-run" -python3 <(curl -fsSL viv.dayl.in/viv.py) run cowsay -- "moove over, pip-run" -``` - -```sh -python -m pip-run requests -- -c "import requests; print(requests.get('https://pypi.org/project/pip-run').status_code)" -python -m viv run requests -b python -- -c "import requests; print(requests.get('https://pypi.org/project/viv').status_code)" -``` - -### [pipx](https://github.com/pypa/pipx/) - -```sh -pipx install pycowsay -viv shim pycowsay -``` - -```sh -pipx run https://gist.githubusercontent.com/cs01/fa721a17a326e551ede048c5088f9e0f/raw/6bdfbb6e9c1132b1c38fdd2f195d4a24c540c324/pipx-demo.py -python3 <(curl -fsSL viv.dayl.in/viv.py) run \ - -s https://gist.githubusercontent.com/cs01/fa721a17a326e551ede048c5088f9e0f/raw/6bdfbb6e9c1132b1c38fdd2f195d4a24c540c324/pipx-demo.py -``` - -## Bonus: use `viv` with just standalone snippet (37LOC) +## Bonus: `viv` standalone `--standalone` will auto-generate a mini function version of `viv` to accomplish the same basic task as using a local copy of `viv`. After generating this standalone `shim` you can freely use this script across unix machines which have `python>3.8`. @@ -58,16 +200,3 @@ See [examples/black](https://github.com/daylinmorgan/viv/blob/dev/examples/black python3 <(curl -fsSL viv.dayl.in/viv.py) shim black -o ./black --standalone --freeze ``` - -[^1]: You do need to have `pip` but surely you have `pip` already. - -[conda-shield]: https://img.shields.io/conda/vn/conda-forge/viv -[conda-url]: https://anaconda.org/conda-forge/viv -[pypi-shield]: https://img.shields.io/pypi/v/viv -[pypi-url]: https://pypi.org/project/viv -[stars-shield]: https://img.shields.io/github/stars/daylinmorgan/viv.svg -[stars-url]: https://github.com/daylinmorgan/viv/stargazers -[issues-shield]: https://img.shields.io/github/issues/daylinmorgan/viv.svg -[issues-url]: https://github.com/daylinmorgan/viv/issues -[license-shield]: https://img.shields.io/github/license/daylinmorgan/viv.svg -[license-url]: https://github.com/daylinmorgan/viv/blob/main/LICENSE diff --git a/docs/vs-others.md b/docs/vs-others.md new file mode 100644 index 0000000..4bd4f02 --- /dev/null +++ b/docs/vs-others.md @@ -0,0 +1,31 @@ +# Vs Others + +## Equivalent commands from alternatives + +### [pip-run](https://github.com/jaraco/pip-run) + + +```sh +pip-run cowsay -- -m cowsay "moove over, pip-run" +python3 <(curl -fsSL viv.dayl.in/viv.py) run cowsay -- "moove over, pip-run" +``` + +```sh +python -m pip-run requests -- -c "import requests; print(requests.get('https://pypi.org/project/pip-run').status_code)" +python -m viv run requests -b python -- -c "import requests; print(requests.get('https://pypi.org/project/viv').status_code)" +``` + +### [pipx](https://github.com/pypa/pipx/) + +```sh +pipx install pycowsay +viv shim pycowsay +``` + +```sh +pipx run https://gist.githubusercontent.com/cs01/fa721a17a326e551ede048c5088f9e0f/raw/6bdfbb6e9c1132b1c38fdd2f195d4a24c540c324/pipx-demo.py +python3 <(curl -fsSL viv.dayl.in/viv.py) run \ + -s https://gist.githubusercontent.com/cs01/fa721a17a326e551ede048c5088f9e0f/raw/6bdfbb6e9c1132b1c38fdd2f195d4a24c540c324/pipx-demo.py +``` + + diff --git a/pdm.lock b/pdm.lock index 5e9821f..b985006 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev", "docs", "test"] strategy = ["cross_platform"] lock_version = "4.4.1" -content_hash = "sha256:67b99f093f433bb1a56dc393d51c615b65e94ea9ad397a5460c5a2a29a21cb22" +content_hash = "sha256:527163383ae4b3489c9082bc814dacd347ee94ad6615c214ba63496eb1bdad2a" [[package]] name = "alabaster" @@ -50,16 +50,6 @@ files = [ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] -[[package]] -name = "cfgv" -version = "3.4.0" -requires_python = ">=3.8" -summary = "Validate configuration and produce human readable error messages." -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - [[package]] name = "charset-normalizer" version = "3.3.2" @@ -155,15 +145,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "distlib" -version = "0.3.8" -summary = "Distribution utilities" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - [[package]] name = "docutils" version = "0.20.1" @@ -184,26 +165,6 @@ files = [ {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] -[[package]] -name = "filelock" -version = "3.13.1" -requires_python = ">=3.8" -summary = "A platform independent file lock." -files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, -] - -[[package]] -name = "identify" -version = "2.5.33" -requires_python = ">=3.8" -summary = "File identification library for Python" -files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, -] - [[package]] name = "idna" version = "3.6" @@ -436,19 +397,6 @@ files = [ {file = "myst_parser-2.0.0.tar.gz", hash = "sha256:ea929a67a6a0b1683cdbe19b8d2e724cd7643f8aa3e7bb18dd65beac3483bead"}, ] -[[package]] -name = "nodeenv" -version = "1.8.0" -requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -summary = "Node.js virtual environment builder" -dependencies = [ - "setuptools", -] -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] - [[package]] name = "packaging" version = "23.2" @@ -468,16 +416,6 @@ files = [ {file = "peppercorn-0.6.tar.gz", hash = "sha256:96d7681d7a04545cfbaf2c6fb66de67b29cfc42421aa263e4c78f2cbb85be4c6"}, ] -[[package]] -name = "platformdirs" -version = "4.1.0" -requires_python = ">=3.8" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, -] - [[package]] name = "pluggy" version = "1.3.0" @@ -488,23 +426,6 @@ files = [ {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] -[[package]] -name = "pre-commit" -version = "3.5.0" -requires_python = ">=3.8" -summary = "A framework for managing and maintaining multi-language pre-commit hooks." -dependencies = [ - "cfgv>=2.0.0", - "identify>=1.0.0", - "nodeenv>=0.11.1", - "pyyaml>=5.1", - "virtualenv>=20.10.0", -] -files = [ - {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, - {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, -] - [[package]] name = "pygments" version = "2.17.2" @@ -632,16 +553,6 @@ files = [ {file = "sampleproject-3.0.0.tar.gz", hash = "sha256:117ed88e5db073bb92969a7545745fd977ee85b7019706dd256a64058f70963d"}, ] -[[package]] -name = "setuptools" -version = "69.0.3" -requires_python = ">=3.8" -summary = "Easily download, build, install, upgrade, and uninstall Python packages" -files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, -] - [[package]] name = "shibuya" version = "2024.1.2" @@ -840,21 +751,6 @@ files = [ {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] -[[package]] -name = "virtualenv" -version = "20.25.0" -requires_python = ">=3.7" -summary = "Virtual Python Environment builder" -dependencies = [ - "distlib<1,>=0.3.7", - "filelock<4,>=3.12.2", - "platformdirs<5,>=3.9.1", -] -files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, -] - [[package]] name = "yartsu" version = "23.5.1b1" diff --git a/pyproject.toml b/pyproject.toml index 29ec599..dc18dc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,20 +21,21 @@ viv = "viv:main" [tool.pdm] version = { source = "scm" } +# need python 3.9 for these which I usually have anyways +ignore_package_warnings = ["sphinx*", "myst-parser"] [tool.pdm.dev-dependencies] dev = [ - "pre-commit>=3", "mypy>=0.991", "astor>=0.8.1", ] docs = [ - "sphinx", - "sphinx-autobuild", - "sphinx-copybutton", - "myst-parser", - "shibuya", - "yartsu" + "sphinx", + "sphinx-autobuild", + "sphinx-copybutton", + "myst-parser", + "shibuya", + "yartsu", ] test = [ "pytest",