Compare commits

..

No commits in common. "852ce927c0476ace28ac96c0cb1296aa75520ea7" and "6d52afe4fe3896886090ace88dce51a65d92839a" have entirely different histories.

36 changed files with 915 additions and 3488 deletions

View file

@ -1,32 +0,0 @@
name: Bug Report
description: File a bug report
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
A bug is when something works differently than it is expected to.
## Remember to search before filing a new report
Please search for this bug in the issue tracker, and use a bug report title that
would have made your bug report turn up in the search results for your search query.
- type: textarea
id: version
attributes:
label: "Viv & python/pip version info"
description: Please include the output of `viv manage show --system` or `python3 <(curl -fsSL viv.dayl.in/viv.py) manage show --system`
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to Reproduce and Observed Behavior
description: What exactly can someone else do, in order to observe the problem that you observed? Include the output and all error messages.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen instead?
validations:
required: true

View file

@ -9,27 +9,24 @@ permissions:
contents: write contents: write
jobs: jobs:
run-tests:
uses: ./.github/workflows/test.yml
build-n-publish: build-n-publish:
needs: run-tests
permissions: permissions:
id-token: write id-token: write
uses: ./.github/workflows/pypi.yml uses: ./.github/workflows/pypi.yml
create-release: create-release:
needs: build-n-publish
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: update latest branch - name: update latest tag
run: | uses: richardsimko/update-tag@v1
git checkout -B latest with:
git push --force-with-lease -u origin latest tag_name: latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate New Release - name: Generate New Release
run: gh release create ${{ github.ref }} run: gh release create ${{ github.ref }}

View file

@ -1,37 +0,0 @@
name: 🧪 Run Tests
on:
pull_request:
workflow_call:
workflow_dispatch:
push:
paths:
- 'src/**/*.py'
- 'tests/**/*.py'
- 'pyproject.toml'
jobs:
run-tests:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8','3.9','3.10','3.11', '3.12']
os:
- ubuntu-latest
- windows-latest
# - macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up PDM
uses: pdm-project/setup-pdm@v3
with:
python-version: ${{ matrix.python-version}}
- name: Install dependencies
run: pdm sync -d -G test
- name: Run Tests
run: pdm run -v pytest tests

7
.gitignore vendored
View file

@ -170,11 +170,8 @@ poetry.toml
# End of https://www.toptal.com/developers/gitignore/api/python # End of https://www.toptal.com/developers/gitignore/api/python
.task.mk .task.mk
# copy of README.md
docs/index.md
docs/cli.md docs/cli.md
docs/viv.py docs/viv.py
docs/svgs docs/svgs
docs/public
/tests/.viv-cache
/scripts/tomli
/scripts/packaging

View file

@ -1,9 +1,13 @@
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/psf/black
rev: v0.1.10 rev: 23.3.0
hooks:
- id: black
language_version: python
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.270'
hooks: hooks:
- id: ruff-format
- id: ruff - id: ruff
args: [ --fix ] args: [--fix, --exit-non-zero-on-fix, --show-fixes]

6
.task.cfg.mk Normal file
View file

@ -0,0 +1,6 @@
USAGE={a.bold}{a.cyan} viv tasks{a.end}:\n
PHONIFY=1
HELP_SEP={a.b_blue}>>>{a.end}
-include .task.mk
$(if $(filter help,$(MAKECMDGOALS)),$(if $(wildcard .task.mk),,.task.mk: ; curl -fsSL https://raw.githubusercontent.com/daylinmorgan/task.mk/v23.1.2/task.mk -o .task.mk))

38
Makefile Normal file
View file

@ -0,0 +1,38 @@
VERSION ?= $(shell git describe --tags --always --dirty=-dev | sed 's/^v//g')
venv: ## generate environment
pdm install
assets/viv-help.svg:
FORCE_COLOR=1 viv --help | yartsu -t 'viv --help' -w 70 -o $@
.PHONY: dev-install
dev-install:
ln -sf $(PWD)/src/viv/viv.py ~/.local/share/viv/viv.py
## docs |> update docs files
docs: docs/viv.py docs/index.md
docs/viv.py: src/viv/viv.py
@cp $< $@
docs/index.md: README.md
@printf -- '---\nhide: [navigation]\n---\n\n' > $@
@cat $< >> $@
examples/black: .FORCE
rm -f $@
viv shim black -y -s -f -o $@
clean: ## remove build artifacts
rm -rf {build,dist}
EXAMPLES = cli.py sys_path.py exe_specific.py frozen_import.py named_env.py scrape.py
generate-example-vivens: ##
for f in $(EXAMPLES); \
do python examples/$$f; done
.FORCE:
.PHONY: .FORCE
-include .task.cfg.mk

View file

@ -29,18 +29,20 @@
Try before you buy! Try before you buy!
```sh ```sh
python3 <(curl -fsSL viv.dayl.in/viv.py) run frogmouth -- gh daylinmorgan/viv python3 <(curl -fsSL viv.dayl.in/viv.py) run pycowsay -- "viv isn't venv\!"
``` ```
--- ---
`Viv` is a standalone dependency-free `venv` creator (just needs python + pip). `Viv` is a standalone dependency-free `venv` creator [^1].
`Viv` helps you ignore silly things like managing temporary or rarely used virtual environments, `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. 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, `Viv`'s uncompromising insistence on portability means that it will always:
only use the standard library and never exceed a single script.
See the [documentation](https://viv.dayl.in) or the [examples](https://github.com/daylinmorgan/viv/tree/main/examples) to get started. 1. only use the standard library
2. never exceed a single script.
For that reason any usage of the `cli` can be accomplished using a remote copy as seen in the below install command.
## Setup ## Setup
@ -50,9 +52,8 @@ Run the below command to install `viv`.
python3 <(curl -fsSL viv.dayl.in/viv.py) manage install python3 <(curl -fsSL viv.dayl.in/viv.py) manage install
``` ```
To access `viv` from within scripts you should add its location to your `PYTHONPATH`. To access `viv` from within scripts you should add it's location to your `PYTHONPATH`.
By default `viv` will be installed to `$XDG_DATA_HOME/viv` or `~/.local/share/viv`; By default `viv` will be installed to `$XDG_DATA_HOME/viv` or `~/.local/share/viv` you can customize this with `--src`.
you can customize this with `--src`.
```sh ```sh
export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/viv" export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/viv"
@ -64,48 +65,34 @@ export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/viv"
pip install viv pip install viv
``` ```
Why is this *not recommended?* Mainly because `viv` is all about hacking your `sys.path`. 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. Placing it in it's own virtual environment or installing in a user site directory may complicate this endeavor.
## Usage ## Usage
In any Python script with external dependencies you can add this line In any python script with external dependencies you can add this line,
to automate `vivenv` creation and installation of dependencies. to automate `vivenv` creation and installation of dependencies.
As a cli:
```sh
viv run frogmouth -- gh daylinmorgan/viv
```
As a python module:
```python ```python
__import__("viv").use("click") __import__("viv").use("click")
``` ```
As an app installer:
```sh
viv shim ruff
```
To remove all `vivenvs` you can use the below command: To remove all `vivenvs` you can use the below command:
```sh ```sh
viv env remove $(viv list -q) viv cache remove $(viv list -q)
``` ```
To remove `viv` altogether you can use the included `purge` command: To remove `viv` all together you can use the included `purge` command:
```sh ```sh
python3 <(curl -fsSL viv.dayl.in/viv.py) manage purge python3 <(curl -fsSL viv.dayl.in/viv.py) manage purge
``` ```
## Equivalent commands from alternatives ## Equivalent commands from alternatives
### [pip-run](https://github.com/jaraco/pip-run) ### [pip-run](https://github.com/jaraco/pip-run)
```sh ```sh
pip-run cowsay -- -m cowsay "moove over, pip-run" pip-run cowsay -- -m cowsay "moove over, pip-run"
python3 <(curl -fsSL viv.dayl.in/viv.py) run cowsay -- "moove over, pip-run" python3 <(curl -fsSL viv.dayl.in/viv.py) run cowsay -- "moove over, pip-run"
@ -113,7 +100,7 @@ python3 <(curl -fsSL viv.dayl.in/viv.py) run cowsay -- "moove over, pip-run"
```sh ```sh
python -m pip-run requests -- -c "import requests; print(requests.get('https://pypi.org/project/pip-run').status_code)" 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)" python -m viv requests -b python -- -c "import requests; print(requests.get('https://pypi.org/project/viv').status_code)"
``` ```
### [pipx](https://github.com/pypa/pipx/) ### [pipx](https://github.com/pypa/pipx/)
@ -131,19 +118,19 @@ python3 <(curl -fsSL viv.dayl.in/viv.py) run \
## Bonus: use `viv` with just standalone snippet (37LOC) ## Bonus: use `viv` with just standalone snippet (37LOC)
`--standalone` will auto-generate a mini-function version of `viv` `--standalone` will auto-generate a mini function version of `viv` to accomplish the same basic task as using a local copy 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`.
After generating this standalone shim you can freely use this script See [examples/black](https://github.com/daylinmorgan/viv/blob/dev/examples/black) for output of below command.
across Unix machines which have Python > 3.8.
See [examples/black](https://github.com/daylinmorgan/viv/blob/dev/examples/black)
for output of the below command.
`viv freeze` also supports `--standalone`. `viv freeze` also supports `--standalone`
```sh ```sh
python3 <(curl -fsSL viv.dayl.in/viv.py) shim black -o ./black --standalone --freeze 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-shield]: https://img.shields.io/conda/vn/conda-forge/viv
[conda-url]: https://anaconda.org/conda-forge/viv [conda-url]: https://anaconda.org/conda-forge/viv
[pypi-shield]: https://img.shields.io/pypi/v/viv [pypi-shield]: https://img.shields.io/pypi/v/viv

View file

@ -1,4 +1,4 @@
<svg class="rich-terminal shadow" viewBox="0 0 890.3333333333334 521.5333333333333" xmlns="http://www.w3.org/2000/svg"> <svg class="rich-terminal shadow" viewBox="0 0 890.3333333333334 545.9333333333333" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io & yartsu https://github.com/daylinmorgan/yartsu --> <!-- Generated with Rich https://www.textualize.io & yartsu https://github.com/daylinmorgan/yartsu -->
<style> <style>
@ -19,14 +19,14 @@
font-weight: 700; font-weight: 700;
} }
.terminal-4099597553-matrix { .terminal-1538990771-matrix {
font-family: Fira Code, monospace; font-family: Fira Code, monospace;
font-size: 20px; font-size: 20px;
line-height: 24.4px; line-height: 24.4px;
font-variant-east-asian: full-width; font-variant-east-asian: full-width;
} }
.terminal-4099597553-title { .terminal-1538990771-title {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
font-family: arial; font-family: arial;
@ -36,98 +36,102 @@
-webkit-filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7)); -webkit-filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7)); filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
} }
.terminal-4099597553-r1 { fill: #94e2d5;font-weight: bold } .terminal-1538990771-r1 { fill: #94e2d5;font-weight: bold }
.terminal-4099597553-r2 { fill: #c6d0f5 } .terminal-1538990771-r2 { fill: #c6d0f5 }
.terminal-4099597553-r3 { fill: #f5c2e7;font-weight: bold } .terminal-1538990771-r3 { fill: #f5c2e7;font-weight: bold }
.terminal-4099597553-r4 { fill: #c6d0f5;font-weight: bold } .terminal-1538990771-r4 { fill: #c6d0f5;font-weight: bold }
.terminal-4099597553-r5 { fill: #f9e2af;font-weight: bold } .terminal-1538990771-r5 { fill: #f9e2af;font-weight: bold }
</style> </style>
<defs> <defs>
<clipPath id="terminal-4099597553-clip-terminal"> <clipPath id="terminal-1538990771-clip-terminal">
<rect x="0" y="0" width="853.0" height="438.2" /> <rect x="0" y="0" width="853.0" height="462.59999999999997" />
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-0"> <clipPath id="terminal-1538990771-line-0">
<rect x="0" y="1.5" width="854" height="24.65"/> <rect x="0" y="1.5" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-1"> <clipPath id="terminal-1538990771-line-1">
<rect x="0" y="25.9" width="854" height="24.65"/> <rect x="0" y="25.9" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-2"> <clipPath id="terminal-1538990771-line-2">
<rect x="0" y="50.3" width="854" height="24.65"/> <rect x="0" y="50.3" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-3"> <clipPath id="terminal-1538990771-line-3">
<rect x="0" y="74.7" width="854" height="24.65"/> <rect x="0" y="74.7" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-4"> <clipPath id="terminal-1538990771-line-4">
<rect x="0" y="99.1" width="854" height="24.65"/> <rect x="0" y="99.1" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-5"> <clipPath id="terminal-1538990771-line-5">
<rect x="0" y="123.5" width="854" height="24.65"/> <rect x="0" y="123.5" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-6"> <clipPath id="terminal-1538990771-line-6">
<rect x="0" y="147.9" width="854" height="24.65"/> <rect x="0" y="147.9" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-7"> <clipPath id="terminal-1538990771-line-7">
<rect x="0" y="172.3" width="854" height="24.65"/> <rect x="0" y="172.3" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-8"> <clipPath id="terminal-1538990771-line-8">
<rect x="0" y="196.7" width="854" height="24.65"/> <rect x="0" y="196.7" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-9"> <clipPath id="terminal-1538990771-line-9">
<rect x="0" y="221.1" width="854" height="24.65"/> <rect x="0" y="221.1" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-10"> <clipPath id="terminal-1538990771-line-10">
<rect x="0" y="245.5" width="854" height="24.65"/> <rect x="0" y="245.5" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-11"> <clipPath id="terminal-1538990771-line-11">
<rect x="0" y="269.9" width="854" height="24.65"/> <rect x="0" y="269.9" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-12"> <clipPath id="terminal-1538990771-line-12">
<rect x="0" y="294.3" width="854" height="24.65"/> <rect x="0" y="294.3" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-13"> <clipPath id="terminal-1538990771-line-13">
<rect x="0" y="318.7" width="854" height="24.65"/> <rect x="0" y="318.7" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-14"> <clipPath id="terminal-1538990771-line-14">
<rect x="0" y="343.1" width="854" height="24.65"/> <rect x="0" y="343.1" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-15"> <clipPath id="terminal-1538990771-line-15">
<rect x="0" y="367.5" width="854" height="24.65"/> <rect x="0" y="367.5" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-4099597553-line-16"> <clipPath id="terminal-1538990771-line-16">
<rect x="0" y="391.9" width="854" height="24.65"/> <rect x="0" y="391.9" width="854" height="24.65"/>
</clipPath> </clipPath>
<clipPath id="terminal-1538990771-line-17">
<rect x="0" y="416.3" width="854" height="24.65"/>
</clipPath>
</defs> </defs>
<rect fill="#1e1e2e" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="10.1667" y="1" width="870" height="487.2" rx="8"/><text class="terminal-4099597553-title" fill="#c6d0f5" text-anchor="middle" x="435" y="27">viv&#160;--help</text> <rect fill="#1e1e2e" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="10.1667" y="1" width="870" height="511.6" rx="8"/><text class="terminal-1538990771-title" fill="#c6d0f5" text-anchor="middle" x="435" y="27">viv&#160;--help</text>
<g transform="translate(32,22)"> <g transform="translate(32,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/> <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/> <circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/> <circle cx="44" cy="0" r="7" fill="#28c840"/>
</g> </g>
<g transform="translate(18.166666666666664, 41) scale(.95)" clip-path="url(#terminal-4099597553-clip-terminal)"> <g transform="translate(18.166666666666664, 41) scale(.95)" clip-path="url(#terminal-1538990771-clip-terminal)">
<g class="terminal-4099597553-matrix"> <g class="terminal-1538990771-matrix">
<text class="terminal-4099597553-r1" x="0" y="20" textLength="61" clip-path="url(#terminal-4099597553-line-0)">usage</text><text class="terminal-4099597553-r2" x="61" y="20" textLength="353.8" clip-path="url(#terminal-4099597553-line-0)">:&#160;viv&#160;[-h]&#160;[-V]&#160;&lt;sub-cmd&gt;&#160;...</text><text class="terminal-4099597553-r2" x="854" y="20" textLength="12.2" clip-path="url(#terminal-4099597553-line-0)"> <text class="terminal-1538990771-r1" x="0" y="20" textLength="61" clip-path="url(#terminal-1538990771-line-0)">usage</text><text class="terminal-1538990771-r2" x="61" y="20" textLength="353.8" clip-path="url(#terminal-1538990771-line-0)">:&#160;viv&#160;[-h]&#160;[-V]&#160;&lt;sub-cmd&gt;&#160;...</text><text class="terminal-1538990771-r2" x="854" y="20" textLength="12.2" clip-path="url(#terminal-1538990771-line-0)">
</text><text class="terminal-4099597553-r2" x="854" y="44.4" textLength="12.2" clip-path="url(#terminal-4099597553-line-1)"> </text><text class="terminal-1538990771-r2" x="854" y="44.4" textLength="12.2" clip-path="url(#terminal-1538990771-line-1)">
</text><text class="terminal-4099597553-r3" x="0" y="68.8" textLength="12.2" clip-path="url(#terminal-4099597553-line-2)">v</text><text class="terminal-4099597553-r1" x="12.2" y="68.8" textLength="24.4" clip-path="url(#terminal-4099597553-line-2)">iv</text><text class="terminal-4099597553-r3" x="48.8" y="68.8" textLength="12.2" clip-path="url(#terminal-4099597553-line-2)">i</text><text class="terminal-4099597553-r1" x="61" y="68.8" textLength="48.8" clip-path="url(#terminal-4099597553-line-2)">sn&#x27;t</text><text class="terminal-4099597553-r3" x="122" y="68.8" textLength="12.2" clip-path="url(#terminal-4099597553-line-2)">v</text><text class="terminal-4099597553-r1" x="134.2" y="68.8" textLength="36.6" clip-path="url(#terminal-4099597553-line-2)">env</text><text class="terminal-4099597553-r2" x="854" y="68.8" textLength="12.2" clip-path="url(#terminal-4099597553-line-2)"> </text><text class="terminal-1538990771-r3" x="0" y="68.8" textLength="12.2" clip-path="url(#terminal-1538990771-line-2)">v</text><text class="terminal-1538990771-r1" x="12.2" y="68.8" textLength="24.4" clip-path="url(#terminal-1538990771-line-2)">iv</text><text class="terminal-1538990771-r3" x="48.8" y="68.8" textLength="12.2" clip-path="url(#terminal-1538990771-line-2)">i</text><text class="terminal-1538990771-r1" x="61" y="68.8" textLength="48.8" clip-path="url(#terminal-1538990771-line-2)">sn&#x27;t</text><text class="terminal-1538990771-r3" x="122" y="68.8" textLength="12.2" clip-path="url(#terminal-1538990771-line-2)">v</text><text class="terminal-1538990771-r1" x="134.2" y="68.8" textLength="36.6" clip-path="url(#terminal-1538990771-line-2)">env</text><text class="terminal-1538990771-r2" x="854" y="68.8" textLength="12.2" clip-path="url(#terminal-1538990771-line-2)">
</text><text class="terminal-4099597553-r2" x="0" y="93.2" textLength="183" clip-path="url(#terminal-4099597553-line-3)">command&#160;line:&#160;`</text><text class="terminal-4099597553-r4" x="183" y="93.2" textLength="475.8" clip-path="url(#terminal-4099597553-line-3)">viv&#160;run&#160;typer&#160;rich-click&#160;-s&#160;./script.py</text><text class="terminal-4099597553-r2" x="658.8" y="93.2" textLength="12.2" clip-path="url(#terminal-4099597553-line-3)">`</text><text class="terminal-4099597553-r2" x="854" y="93.2" textLength="12.2" clip-path="url(#terminal-4099597553-line-3)"> </text><text class="terminal-1538990771-r2" x="0" y="93.2" textLength="183" clip-path="url(#terminal-1538990771-line-3)">command&#160;line:&#160;`</text><text class="terminal-1538990771-r4" x="183" y="93.2" textLength="475.8" clip-path="url(#terminal-1538990771-line-3)">viv&#160;run&#160;typer&#160;rich-click&#160;-s&#160;./script.py</text><text class="terminal-1538990771-r2" x="658.8" y="93.2" textLength="12.2" clip-path="url(#terminal-1538990771-line-3)">`</text><text class="terminal-1538990771-r2" x="854" y="93.2" textLength="12.2" clip-path="url(#terminal-1538990771-line-3)">
</text><text class="terminal-4099597553-r2" x="0" y="117.6" textLength="146.4" clip-path="url(#terminal-4099597553-line-4)">python&#160;api:&#160;</text><text class="terminal-4099597553-r4" x="146.4" y="117.6" textLength="536.8" clip-path="url(#terminal-4099597553-line-4)">__import__(&quot;viv&quot;).use(&quot;typer&quot;,&#160;&quot;rich-click&quot;)</text><text class="terminal-4099597553-r2" x="854" y="117.6" textLength="12.2" clip-path="url(#terminal-4099597553-line-4)"> </text><text class="terminal-1538990771-r2" x="0" y="117.6" textLength="146.4" clip-path="url(#terminal-1538990771-line-4)">python&#160;api:&#160;</text><text class="terminal-1538990771-r4" x="146.4" y="117.6" textLength="536.8" clip-path="url(#terminal-1538990771-line-4)">__import__(&quot;viv&quot;).use(&quot;typer&quot;,&#160;&quot;rich-click&quot;)</text><text class="terminal-1538990771-r2" x="854" y="117.6" textLength="12.2" clip-path="url(#terminal-1538990771-line-4)">
</text><text class="terminal-4099597553-r2" x="854" y="142" textLength="12.2" clip-path="url(#terminal-4099597553-line-5)"> </text><text class="terminal-1538990771-r2" x="854" y="142" textLength="12.2" clip-path="url(#terminal-1538990771-line-5)">
</text><text class="terminal-4099597553-r1" x="0" y="166.4" textLength="85.4" clip-path="url(#terminal-4099597553-line-6)">options</text><text class="terminal-4099597553-r2" x="85.4" y="166.4" textLength="12.2" clip-path="url(#terminal-4099597553-line-6)">:</text><text class="terminal-4099597553-r2" x="854" y="166.4" textLength="12.2" clip-path="url(#terminal-4099597553-line-6)"> </text><text class="terminal-1538990771-r1" x="0" y="166.4" textLength="85.4" clip-path="url(#terminal-1538990771-line-6)">options</text><text class="terminal-1538990771-r2" x="85.4" y="166.4" textLength="12.2" clip-path="url(#terminal-1538990771-line-6)">:</text><text class="terminal-1538990771-r2" x="854" y="166.4" textLength="12.2" clip-path="url(#terminal-1538990771-line-6)">
</text><text class="terminal-4099597553-r5" x="24.4" y="190.8" textLength="24.4" clip-path="url(#terminal-4099597553-line-7)">-h</text><text class="terminal-4099597553-r2" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-4099597553-line-7)">,&#160;</text><text class="terminal-4099597553-r5" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-4099597553-line-7)">--help</text><text class="terminal-4099597553-r2" x="146.4" y="190.8" textLength="463.6" clip-path="url(#terminal-4099597553-line-7)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;show&#160;this&#160;help&#160;message&#160;and&#160;exit</text><text class="terminal-4099597553-r2" x="854" y="190.8" textLength="12.2" clip-path="url(#terminal-4099597553-line-7)"> </text><text class="terminal-1538990771-r5" x="24.4" y="190.8" textLength="24.4" clip-path="url(#terminal-1538990771-line-7)">-h</text><text class="terminal-1538990771-r2" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-1538990771-line-7)">,&#160;</text><text class="terminal-1538990771-r5" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-1538990771-line-7)">--help</text><text class="terminal-1538990771-r2" x="146.4" y="190.8" textLength="463.6" clip-path="url(#terminal-1538990771-line-7)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;show&#160;this&#160;help&#160;message&#160;and&#160;exit</text><text class="terminal-1538990771-r2" x="854" y="190.8" textLength="12.2" clip-path="url(#terminal-1538990771-line-7)">
</text><text class="terminal-4099597553-r5" x="24.4" y="215.2" textLength="24.4" clip-path="url(#terminal-4099597553-line-8)">-V</text><text class="terminal-4099597553-r2" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-4099597553-line-8)">,&#160;</text><text class="terminal-4099597553-r5" x="73.2" y="215.2" textLength="109.8" clip-path="url(#terminal-4099597553-line-8)">--version</text><text class="terminal-4099597553-r2" x="183" y="215.2" textLength="512.4" clip-path="url(#terminal-4099597553-line-8)">&#160;&#160;&#160;&#160;show&#160;program&#x27;s&#160;version&#160;number&#160;and&#160;exit</text><text class="terminal-4099597553-r2" x="854" y="215.2" textLength="12.2" clip-path="url(#terminal-4099597553-line-8)"> </text><text class="terminal-1538990771-r5" x="24.4" y="215.2" textLength="24.4" clip-path="url(#terminal-1538990771-line-8)">-V</text><text class="terminal-1538990771-r2" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-1538990771-line-8)">,&#160;</text><text class="terminal-1538990771-r5" x="73.2" y="215.2" textLength="109.8" clip-path="url(#terminal-1538990771-line-8)">--version</text><text class="terminal-1538990771-r2" x="183" y="215.2" textLength="512.4" clip-path="url(#terminal-1538990771-line-8)">&#160;&#160;&#160;&#160;show&#160;program&#x27;s&#160;version&#160;number&#160;and&#160;exit</text><text class="terminal-1538990771-r2" x="854" y="215.2" textLength="12.2" clip-path="url(#terminal-1538990771-line-8)">
</text><text class="terminal-4099597553-r2" x="854" y="239.6" textLength="12.2" clip-path="url(#terminal-4099597553-line-9)"> </text><text class="terminal-1538990771-r2" x="854" y="239.6" textLength="12.2" clip-path="url(#terminal-1538990771-line-9)">
</text><text class="terminal-4099597553-r1" x="0" y="264" textLength="134.2" clip-path="url(#terminal-4099597553-line-10)">subcommands</text><text class="terminal-4099597553-r2" x="134.2" y="264" textLength="12.2" clip-path="url(#terminal-4099597553-line-10)">:</text><text class="terminal-4099597553-r2" x="854" y="264" textLength="12.2" clip-path="url(#terminal-4099597553-line-10)"> </text><text class="terminal-1538990771-r1" x="0" y="264" textLength="134.2" clip-path="url(#terminal-1538990771-line-10)">subcommands</text><text class="terminal-1538990771-r2" x="134.2" y="264" textLength="12.2" clip-path="url(#terminal-1538990771-line-10)">:</text><text class="terminal-1538990771-r2" x="854" y="264" textLength="12.2" clip-path="url(#terminal-1538990771-line-10)">
</text><text class="terminal-4099597553-r5" x="24.4" y="288.4" textLength="109.8" clip-path="url(#terminal-4099597553-line-11)">&lt;sub-cmd&gt;</text><text class="terminal-4099597553-r2" x="854" y="288.4" textLength="12.2" clip-path="url(#terminal-4099597553-line-11)"> </text><text class="terminal-1538990771-r5" x="24.4" y="288.4" textLength="109.8" clip-path="url(#terminal-1538990771-line-11)">&lt;sub-cmd&gt;</text><text class="terminal-1538990771-r2" x="854" y="288.4" textLength="12.2" clip-path="url(#terminal-1538990771-line-11)">
</text><text class="terminal-4099597553-r5" x="48.8" y="312.8" textLength="97.6" clip-path="url(#terminal-4099597553-line-12)">list&#160;(l)</text><text class="terminal-4099597553-r2" x="146.4" y="312.8" textLength="231.8" clip-path="url(#terminal-4099597553-line-12)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;list&#160;vivenvs</text><text class="terminal-4099597553-r2" x="854" y="312.8" textLength="12.2" clip-path="url(#terminal-4099597553-line-12)"> </text><text class="terminal-1538990771-r5" x="48.8" y="312.8" textLength="97.6" clip-path="url(#terminal-1538990771-line-12)">list&#160;(l)</text><text class="terminal-1538990771-r2" x="146.4" y="312.8" textLength="231.8" clip-path="url(#terminal-1538990771-line-12)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;list&#160;vivenvs</text><text class="terminal-1538990771-r2" x="854" y="312.8" textLength="12.2" clip-path="url(#terminal-1538990771-line-12)">
</text><text class="terminal-4099597553-r5" x="48.8" y="337.2" textLength="97.6" clip-path="url(#terminal-4099597553-line-13)">shim&#160;(s)</text><text class="terminal-4099597553-r2" x="146.4" y="337.2" textLength="439.2" clip-path="url(#terminal-4099597553-line-13)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;generate&#160;viv-powered&#160;cli&#160;apps</text><text class="terminal-4099597553-r2" x="854" y="337.2" textLength="12.2" clip-path="url(#terminal-4099597553-line-13)"> </text><text class="terminal-1538990771-r5" x="48.8" y="337.2" textLength="97.6" clip-path="url(#terminal-1538990771-line-13)">shim&#160;(s)</text><text class="terminal-1538990771-r2" x="146.4" y="337.2" textLength="439.2" clip-path="url(#terminal-1538990771-line-13)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;generate&#160;viv-powered&#160;cli&#160;apps</text><text class="terminal-1538990771-r2" x="854" y="337.2" textLength="12.2" clip-path="url(#terminal-1538990771-line-13)">
</text><text class="terminal-4099597553-r5" x="48.8" y="361.6" textLength="85.4" clip-path="url(#terminal-4099597553-line-14)">run&#160;(r)</text><text class="terminal-4099597553-r2" x="134.2" y="361.6" textLength="585.6" clip-path="url(#terminal-4099597553-line-14)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;run&#160;an&#160;app/script&#160;with&#160;an&#160;on-demand&#160;venv</text><text class="terminal-4099597553-r2" x="854" y="361.6" textLength="12.2" clip-path="url(#terminal-4099597553-line-14)"> </text><text class="terminal-1538990771-r5" x="48.8" y="361.6" textLength="85.4" clip-path="url(#terminal-1538990771-line-14)">run&#160;(r)</text><text class="terminal-1538990771-r2" x="134.2" y="361.6" textLength="585.6" clip-path="url(#terminal-1538990771-line-14)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;run&#160;an&#160;app/script&#160;with&#160;an&#160;on-demand&#160;venv</text><text class="terminal-1538990771-r2" x="854" y="361.6" textLength="12.2" clip-path="url(#terminal-1538990771-line-14)">
</text><text class="terminal-4099597553-r5" x="48.8" y="386" textLength="85.4" clip-path="url(#terminal-4099597553-line-15)">env&#160;(e)</text><text class="terminal-4099597553-r2" x="134.2" y="386" textLength="427" clip-path="url(#terminal-4099597553-line-15)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;manage&#160;the&#160;viv&#160;vivenv&#160;cache</text><text class="terminal-4099597553-r2" x="854" y="386" textLength="12.2" clip-path="url(#terminal-4099597553-line-15)"> </text><text class="terminal-1538990771-r5" x="48.8" y="386" textLength="85.4" clip-path="url(#terminal-1538990771-line-15)">exe&#160;(e)</text><text class="terminal-1538990771-r2" x="134.2" y="386" textLength="536.8" clip-path="url(#terminal-1538990771-line-15)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;run&#160;binary/script&#160;in&#160;existing&#160;vivenv</text><text class="terminal-1538990771-r2" x="854" y="386" textLength="12.2" clip-path="url(#terminal-1538990771-line-15)">
</text><text class="terminal-4099597553-r5" x="48.8" y="410.4" textLength="122" clip-path="url(#terminal-4099597553-line-16)">freeze&#160;(f)</text><text class="terminal-4099597553-r2" x="170.8" y="410.4" textLength="561.2" clip-path="url(#terminal-4099597553-line-16)">&#160;&#160;&#160;&#160;&#160;create&#160;import&#160;statement&#160;from&#160;package&#160;spec</text><text class="terminal-4099597553-r2" x="854" y="410.4" textLength="12.2" clip-path="url(#terminal-4099597553-line-16)"> </text><text class="terminal-1538990771-r5" x="48.8" y="410.4" textLength="109.8" clip-path="url(#terminal-1538990771-line-16)">cache&#160;(c)</text><text class="terminal-1538990771-r2" x="158.6" y="410.4" textLength="402.6" clip-path="url(#terminal-1538990771-line-16)">&#160;&#160;&#160;&#160;&#160;&#160;manage&#160;the&#160;viv&#160;vivenv&#160;cache</text><text class="terminal-1538990771-r2" x="854" y="410.4" textLength="12.2" clip-path="url(#terminal-1538990771-line-16)">
</text><text class="terminal-4099597553-r5" x="48.8" y="434.8" textLength="122" clip-path="url(#terminal-4099597553-line-17)">manage&#160;(m)</text><text class="terminal-4099597553-r2" x="170.8" y="434.8" textLength="268.4" clip-path="url(#terminal-4099597553-line-17)">&#160;&#160;&#160;&#160;&#160;manage&#160;viv&#160;itself</text><text class="terminal-4099597553-r2" x="854" y="434.8" textLength="12.2" clip-path="url(#terminal-4099597553-line-17)"> </text><text class="terminal-1538990771-r5" x="48.8" y="434.8" textLength="122" clip-path="url(#terminal-1538990771-line-17)">freeze&#160;(f)</text><text class="terminal-1538990771-r2" x="170.8" y="434.8" textLength="561.2" clip-path="url(#terminal-1538990771-line-17)">&#160;&#160;&#160;&#160;&#160;create&#160;import&#160;statement&#160;from&#160;package&#160;spec</text><text class="terminal-1538990771-r2" x="854" y="434.8" textLength="12.2" clip-path="url(#terminal-1538990771-line-17)">
</text><text class="terminal-1538990771-r5" x="48.8" y="459.2" textLength="122" clip-path="url(#terminal-1538990771-line-18)">manage&#160;(m)</text><text class="terminal-1538990771-r2" x="170.8" y="459.2" textLength="268.4" clip-path="url(#terminal-1538990771-line-18)">&#160;&#160;&#160;&#160;&#160;manage&#160;viv&#160;itself</text><text class="terminal-1538990771-r2" x="854" y="459.2" textLength="12.2" clip-path="url(#terminal-1538990771-line-18)">
</text> </text>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,23 +0,0 @@
project = "Viv"
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"]
html_extra_path = ["viv.py"]
html_theme = "shibuya"
# html_static_path = ["_static"]
html_logo = "../assets/logo.svg"
html_theme_options = {
"github_url": "https://github.com/daylinmorgan/viv",
"nav_links": [
{"title": "Documentation", "url": "installation"},
{"title": "CLI Reference", "url": "cli"},
],
}

View file

@ -1,45 +0,0 @@
# 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` 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 for 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 appropriately declare it as a 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

View file

@ -1,40 +0,0 @@
---
layout: landing
cover: http://viv.dayl.in/_static/logo.svg
desciption: Viv isn't venv
---
# viv isn't venv
Try before you buy!
```sh
python3 <(curl -fsSL viv.dayl.in/viv.py) run frogmouth -- gh daylinmorgan/viv
```
:::{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,
only use the standard library and never exceed a single script.
```{toctree}
:hidden:
:maxdepth: 2
installation.md
usage.md
configuration.md
vs-others.md
cli.md
```

View file

@ -1,56 +0,0 @@
# 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.

View file

@ -1,202 +0,0 @@
# Usage
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
```
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 <hash>`
:::{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`, `spec` and `files`.
To remove all `vivenvs` you can use the below command:
```sh
viv env remove $(viv list -q)
```
To remove `viv` all together you can use the included `purge` command:
```sh
python3 <(curl -fsSL viv.dayl.in/viv.py) manage purge
```
## 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`.
See [examples/black](https://github.com/daylinmorgan/viv/blob/dev/examples/black) for output of below command.
`viv freeze` also supports `--standalone`
```sh
python3 <(curl -fsSL viv.dayl.in/viv.py) shim black -o ./black --standalone --freeze
```

View file

@ -1,31 +0,0 @@
# 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
```

View file

@ -7,7 +7,6 @@ it will generate a new vivenv.
It may be important to require a exe specificty if you are frequently running It may be important to require a exe specificty if you are frequently running
different version of pythons and rely on c extension modules as in numpy. different version of pythons and rely on c extension modules as in numpy.
""" """
__import__("viv").use("numpy", "plotext", track_exe=True) # noqa __import__("viv").use("numpy", "plotext", track_exe=True) # noqa
import numpy as np import numpy as np

View file

@ -6,7 +6,6 @@ This import statement was generated using
Using viv freeze ensures future runs of this Using viv freeze ensures future runs of this
script will use the same essential environment script will use the same essential environment
""" """
__import__("viv").use( __import__("viv").use(
"numpy==1.24.0", "numpy==1.24.0",
"pandas==1.5.2", "pandas==1.5.2",

17
examples/pep722.py Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env -S viv run -s
# In order to run, this script needs the following 3rd party libraries
#
# Script Dependencies:
# requests
# rich # Needed for the output
#
# # Not needed - just to show that fragments in URLs do not
# # get treated as comments
# pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=2e15a2d4e7e9f394a9c7a6c905c6a239402a6442
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])

View file

@ -1,15 +0,0 @@
#!/usr/bin/env -S viv run -s
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
import requests
from rich import print
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
print([(k, v["title"]) for k, v in data.items()][:10])

View file

@ -1,17 +0,0 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
__import__("viv").run()
import requests
from rich import print
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
print([(k, v["title"]) for k, v in data.items()][:10])

0
examples/stopwatch.py Normal file → Executable file
View file

View file

@ -2,13 +2,14 @@
""" """
Embed the viv.py on the sys.path at runtime rather than using PYTHONPATH Embed the viv.py on the sys.path at runtime rather than using PYTHONPATH
""" """
import sys import sys
old_sys_path = sys.path.copy() # noqa old_sys_path = sys.path.copy() # noqa
__import__("sys").path.append(__import__("os").path.expanduser("~/.local/share/viv")) # noqa # isort: off __import__("sys").path.append(
__import__("os").path.expanduser("~/.local/share/viv")
) # noqa # isort: off
__import__("viv").use("rich") # noqa # isort: off __import__("viv").use("rich") # noqa # isort: off
from difflib import unified_diff from difflib import unified_diff

35
mkdocs.yml Normal file
View file

@ -0,0 +1,35 @@
site_name: viv
site_url: https://viv.dayl.in
repo_url: https://github.com/daylinmorgan/viv
edit_uri: edit/main/docs/
repo_name: daylinmorgan/viv
nav:
- home: index.md
- cli: cli.md
theme:
name: material
logo: https://raw.githubusercontent.com/daylinmorgan/viv/main/assets/logo.svg
favicon: https://raw.githubusercontent.com/daylinmorgan/viv/main/assets/logo.svg
features:
- navigation.tabs
- navigation.indexes
palette:
# Palette toggle for dark mode
- scheme: slate
primary: deep purple
toggle:
icon: material/brightness-4
name: Switch to light mode
# Palette toggle for light mode
- scheme: default
primary: deep purple
toggle:
icon: material/brightness-7
name: Switch to dark mode
markdown_extensions:
- footnotes

View file

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import shutil
from pathlib import Path from pathlib import Path
import nox import nox
@ -43,11 +42,11 @@ def docs(session):
if not Path("docs/svgs").is_dir(): if not Path("docs/svgs").is_dir():
svgs(session) svgs(session)
shutil.copyfile("src/viv/viv.py", "docs/viv.py") session.run("make", "docs", external=True)
if session.interactive: if session.interactive:
session.run("sphinx-autobuild", "docs", "site", "--port", "8787") session.run("mkdocs", "serve")
else: else:
session.run("sphinx-build", "docs", "site") session.run("mkdocs", "build")
@nox.session @nox.session
@ -55,7 +54,10 @@ def release(session):
session.run("./scripts/release.py", external=True) session.run("./scripts/release.py", external=True)
@nox.session(python=["3.8", "3.9", "3.10", "3.11", "3.12"]) # @nox.session(
def test(session): # python=["3.8", "3.9", "3.10", "3.11"]
pdm_install(session, "test") # )
session.run("pytest", "tests/") # def test(session):
# pdm_install(session,'test')
# session.run('pytest')
#

884
pdm.lock generated

File diff suppressed because it is too large Load diff

View file

@ -21,28 +21,18 @@ viv = "viv:main"
[tool.pdm] [tool.pdm]
version = { source = "scm" } version = { source = "scm" }
# need python 3.9 for these which I usually have anyways
ignore_package_warnings = ["sphinx*", "myst-parser"]
[tool.pdm.dev-dependencies] [tool.pdm.dev-dependencies]
dev = [ dev = [
"pre-commit>=3",
"mypy>=0.991", "mypy>=0.991",
"astor>=0.8.1",
] ]
docs = [ docs = [
"sphinx", "mkdocs-material>=9.1.15",
"sphinx-autobuild", "yartsu"
"sphinx-copybutton",
"myst-parser",
"shibuya",
"yartsu",
] ]
test = [
"pytest",
"sampleproject"
]
[tool.ruff.lint] [tool.ruff]
select = ["E","F","I"] select = ["E","F","I"]
ignore = ["E402"] ignore = ["E402"]

View file

@ -15,6 +15,7 @@ CLI_DOC_PATH = DOCS_PATH / "cli.md"
cmds := dict.fromkeys( cmds := dict.fromkeys(
( (
"list", "list",
"exe",
"manage", "manage",
"freeze", "freeze",
"shim", "shim",
@ -23,10 +24,7 @@ CLI_DOC_PATH = DOCS_PATH / "cli.md"
[], [],
) )
).update( ).update(
{ {"manage": ["update", "purge", "show", "install"], "cache": ["info", "remove"]},
"manage": ["update", "purge", "show", "install"],
"env": ["exe", "info", "remove"],
},
) )
@ -35,7 +33,7 @@ cli_doc = """\
hide: [navigation] hide: [navigation]
--- ---
# CLI Reference # cli
![help](./svgs/viv-help.svg) ![help](./svgs/viv-help.svg)
""" """

View file

@ -46,14 +46,11 @@ def release():
next = f"{datetime.now().year}.{inc_build(build)}" next = f"{datetime.now().year}.{inc_build(build)}"
msg = f"bump {current} -> {next}" msg = f"bump {current} -> {next}"
FILE.write_text( FILE.write_text(
re.sub(r'__version__ = ".*"', f'__version__ = "{next}"', FILE.read_text()) re.sub(r'__version__ = "[\d\.]+"', f'__version__ = "{next}"', FILE.read_text())
) )
subprocess.run(["git", "add", FILE]) subprocess.run(["git", "add", FILE])
subprocess.run(["git", "commit", "-m", msg, "--no-verify"]) subprocess.run(["git", "commit", "-m", msg, "--no-verify"])
subprocess.run(["git", "tag", f"v{next}"]) subprocess.run(["git", "tag", f"v{next}"])
FILE.write_text(
re.sub(r'__version__ = ".*"', f'__version__ = "{next}-dev"', FILE.read_text())
)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -1,222 +0,0 @@
#!/usr/bin/env python3
import ast
import re
import subprocess
import textwrap
from pathlib import Path
from typing import List
import astor
def remove_docs_and_comments(code):
parsed = ast.parse(code)
for node in ast.walk(parsed):
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Str):
# set value to empty string which should be ignored by astor.to_source
node.value = ast.Constant(value="")
elif (
(isinstance(node, ast.FunctionDef) or isinstance(node, ast.ClassDef))
and len(node.body) == 1
and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, ast.Constant)
):
# add pass to empty functions and class definition
node.body = [ast.Pass()]
formatted_code = astor.to_source(parsed)
pattern = r'^.*"""""".*$' # remove empty """"""
formatted_code = re.sub(pattern, "", formatted_code, flags=re.MULTILINE)
return formatted_code
class Package:
def __init__(
self,
name: str,
url: str,
files: List[tuple[str, List[List[int]]]],
rev: str,
basepath: Path,
imports: str = "",
prefix: str = "",
suffix: str = "",
indent: bool = False,
):
self.name = name
self.files = files
self.url = url
self.rev = rev
self.basepath = basepath
self.imports = imports
self.prefix = prefix
self.suffix = suffix
self.indent = indent
self.ensure()
self.generate_vendored_source()
self.replace_identifiers()
def ensure(self):
dir = Path(__file__).parent / self.name
if not dir.is_dir():
subprocess.run(["git", "clone", self.url, dir])
subprocess.run(["git", "-C", dir, "checkout", self.rev])
@property
def start_delim(self) -> str:
return f"#### START VENDORED {self.name.upper()} ####"
@property
def end_delim(self) -> str:
return f"#### END VENDORED {self.name.upper()} ####"
def generate_vendored_source(self):
self.src_text = ""
for f, slices in self.files:
og_text = (self.basepath / f"{f}.py").read_text()
for indices in slices:
self.src_text = "\n".join(
(
self.src_text,
*[
line
for line in og_text.splitlines()[slice(*indices)]
if line.strip("\r\n")
],
)
)
def replace_identifiers(self):
patterns = set.union(
*[
set(re.findall(regex, self.src_text, re.MULTILINE))
for regex in (
r"^class (?P<class>[a-zA-Z_]*)(?:\(.*\))?:",
r"^(?P<ident>[a-zA-Z_]*) =",
r"^def (?P<function>[a-zA-Z_]+)\(",
)
]
) - {
"Key",
} # prevent KeyError false positive by leaving Key alone
for pat in patterns:
self.src_text = re.sub(
r'(?P<lead>[\s("\[={])' + pat,
f"\g<lead>v_{self.name}_{pat}",
self.src_text,
)
def insert(self, base_text: str) -> str:
start, rest = re.split(self.start_delim, base_text)
_, rest = re.split(self.end_delim, base_text)
src = textwrap.indent(
remove_docs_and_comments(self.src_text.strip()),
prefix=" " * (4 if self.indent else 0),
)
return "\n".join(
(
start.strip(),
"\n",
self.start_delim,
self.prefix + self.imports + src + self.suffix,
self.end_delim,
"\n",
rest.strip(),
)
)
PACKAGES = [
Package(
name="packaging",
url="https://github.com/pypa/packaging.git",
# v24.0
rev="7a983f7f0068669ead9d4f7571be24d6c0d83eb9",
files=(
("_structures", [[5, 61]]),
("version", [[17, 563]]),
("utils", [[54, 100]]),
("specifiers", [[18, 1017]]),
),
basepath=Path(__file__).parent / "packaging/src/packaging",
prefix="""
# MODIFIED FROM https://github.com/pypa/packaging
# see repo for original licenses
# This software is made available under the terms of *either* of the licenses
# found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
# under the terms of *both* these licenses.
""",
imports="""
import abc # noqa
import itertools # noqa
import re # noqa
from typing import ( # noqa
Any,
Callable,
Iterable,
Iterator,
List,
NamedTuple,
Optional,
Set,
SupportsInt,
Tuple,
TypeVar,
Union,
)
""",
suffix="""
Version = v_packaging_Version
SpecifierSet = v_packaging_SpecifierSet
""",
),
Package(
name="tomli",
url="https://github.com/hukkin/tomli.git",
# rev="2.0.1",
rev="a6138675bcca68eea5b8abec7c2ec06d57f965a0",
files=(
("_types", [[7, 11]]),
("_re", [[14, 107]]),
("_parser", [[20, 691]]),
),
prefix="""
if sys.version_info >= (3, 11):
from tomllib import loads as toml_loads
else:
# MODIFIED FROM https://github.com/hukkin/tomli
# see below for original license
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
# Licensed to PSF under a Contributor Agreement.
""",
imports="""
import string # noqa
from collections.abc import Iterable # noqa
from functools import lru_cache # noqa
from datetime import date, datetime, time, timedelta, timezone, tzinfo # noqa
from types import MappingProxyType # noqa
from typing import IO, Any, Callable, NamedTuple # noqa
""",
basepath=Path(__file__).parent / "tomli/src/tomli",
suffix="""
toml_loads = v_tomli_loads
""",
indent=True,
),
]
def main():
viv_source_path = Path(__file__).parent.parent / "src/viv/viv.py"
viv_source = viv_source_path.read_text()
for pkg in PACKAGES:
viv_source = pkg.insert(viv_source)
viv_source_path.write_text(viv_source)
if __name__ == "__main__":
main()

View file

@ -1 +1 @@
from .viv import __version__, use, main, run # noqa from .viv import __version__, use, main # noqa

File diff suppressed because it is too large Load diff

45
task
View file

@ -1,45 +0,0 @@
#!/usr/bin/env bash
# VERSION = $(git describe --tags --always --dirty=-dev | sed 's/^v//g')
function task:venv {
: "setup up pdm venv"
pdm install
}
function task:dev-install {
: "symlink development version"
mkdir -p ~/.local/share/viv
mkdir -p ~/.local/bin
ln -sf "$(pwd)/src/viv/viv.py" ~/.local/share/viv/viv.py
ln -sf ~/.local/share/viv/viv.py ~/.local/bin/viv
}
function task:_black {
: "generate black example shim"
rm -f examples/black
viv shim black -y -s -f -o examples/black
}
function task:clean {
: "clean build artifacts"
rm -rf build dist
}
function task:examples {
: "run examples to generate vivenvs"
examples="cli sys_path exe_specific frozen_import named_env scrape"
for f in $examples; do
python "examples/$f.py"
done
}
function task:_help-logo {
FORCE_COLOR=1 viv --help | yartsu -t 'viv --help' -w 70 -o assets/viv-help.svg
}
# ---- do-task boilerplate ----
function task:help { : "Show this help"; echo "do:";w=$(("$(compgen -A function | wc -L)" - 3));while read -r name; do [[ ! $name =~ ^task:_ ]] && [[ $name =~ ^task: ]] && paste <(printf '\033[1;32m%*s\033[0m\n' "$w" "${name#task:}") <(type "$name" | sed -nEe 's/^[[:space:]]*: ?"(.*)";/\1/p'); done < <(compgen -A function); }
while read -r name; do [[ $name == "task:$1" ]] && { shift; task="$name"; }; done < <(compgen -A function)
[[ -n "$1" && -z "$task" ]] && printf "\033[1;31m%s\033\0[m is not a task\n" "$1"
"${task:-task:help}" "$@" && exit "$?"

View file

@ -1,12 +0,0 @@
import os
import shutil
from pathlib import Path
cache = (Path(__file__).parent / ".viv-cache").absolute()
if cache.is_dir():
shutil.rmtree(cache)
# remove local settings
os.environ = {k: v for k, v in os.environ.items() if not k.startswith("VIV_")}
os.environ["VIV_CACHE"] = str(cache)

View file

@ -1,52 +0,0 @@
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

View file

@ -1,17 +0,0 @@
import sys
import pytest
from viv import use
def test_use():
with pytest.raises(ImportError):
import pyjokes # noqa
use("pyjokes")
import pyjokes # noqa
with pytest.raises(ImportError):
from sample.simple import add_one # noqa
assert len([p for p in sys.path if "site-packages" in p]) == 1

View file

@ -1,17 +0,0 @@
from viv.viv import SpecifierSet, Version, toml_loads
def test_packaging():
assert Version("3.6") in SpecifierSet(">=3.6")
def test_tomli():
assert {"requires-python": ">3.6", "dependencies": ["rich", "typer"]} == toml_loads(
"""
requires-python = ">3.6"
dependencies = [
"rich",
"typer"
]
"""
)