Compare commits

...

73 commits

Author SHA1 Message Date
852ce927c0
- 2024-07-12 12:33:22 -05:00
28c268b8f0
dir!=files 2024-07-12 12:30:53 -05:00
247f80921d
fix: add key to remove sort 2024-07-12 12:25:05 -05:00
960d7acf26
ci: use latest branch over tag 2024-07-12 11:43:52 -05:00
68e12d643c
tests: add python v3.12 2024-07-12 11:40:33 -05:00
cf2414c0d1
bump 2024.1004 -> 2024.1005 2024-07-12 11:38:45 -05:00
303831bdb8
fix(#23): add pip version check 2024-07-09 12:03:06 -05:00
8aced00ace
docs: fix url 2024-06-18 14:07:12 -05:00
1b8bcb4454
feat(#19): add --generate flag for viv shim 2024-04-18 15:02:00 -05:00
c43f2f027c
chore: bump packaging to v24 2024-04-18 15:02:00 -05:00
4c236e96eb
fix(#18): use --bin to determine default filename 2024-04-18 15:02:00 -05:00
c5bc03f497
chore: remove newlines 2024-01-19 10:20:51 -06:00
8fb6648e8e
docs: remove old mkdocs config 2024-01-19 10:20:39 -06:00
f97a617ac9
docs: remove redundant phrase 2024-01-10 11:36:29 -06:00
9eba6a303f
refactor: abstract script/cache behavior more 2024-01-09 17:58:33 -06:00
6ef2f765d6
chore: update type checking 2024-01-09 02:29:44 -06:00
0e5ac7c9c9
refactor: add PYTHONPATH to manage show --system for debugging 2024-01-09 02:17:10 -06:00
9d237d3b96
docs: minor changes 2024-01-09 02:00:43 -06:00
6357a99189
ci: map not list 2024-01-09 01:44:17 -06:00
b822fe432c
ci: only run tests when source changes 2024-01-09 01:43:43 -06:00
032e23af95
docs: finish updating docs 2024-01-09 01:41:34 -06:00
13cb21435d
docs: frogmouth is a cooler demo 2024-01-05 17:23:34 -06:00
38b6e4d746
bump 2023.1003 -> 2024.1004 2024-01-05 17:21:04 -06:00
be3c7f83c9
docs: actually document the things 2024-01-05 17:20:57 -06:00
5dacf8ab60
refactor: abstract _uses_viv + add tests 2024-01-04 15:53:04 -06:00
e31d401585
fix: remove erroneous debug print 2024-01-04 14:14:58 -06:00
f53d00e8e2
feat: add viv.run() to accompany inline metatdata approach 2024-01-03 13:49:27 -06:00
f21b90d962
fix: use _get_user with _update_cache 2024-01-03 13:47:47 -06:00
1bb48eedbc
chore: RIP PEP722 2024-01-03 13:13:24 -06:00
5fb501aa2c fix: run vendored test on package 2024-01-03 13:09:19 -06:00
b2a2597816 fix: use most recent tomli 2024-01-03 13:09:19 -06:00
523a9a9d06 feat: vendor packaging + add support for requires python check
This is a mostly small change albeit with addition of more embedded
code from pypa/packaging to simplify supporting a requires-python check
with PEP723 style syntax.
2024-01-03 13:09:19 -06:00
f16ded2a83 fix: use updated pep723 syntax 2024-01-03 13:09:19 -06:00
c8051f9513 refactor: add tomllib preference 2024-01-03 13:09:19 -06:00
12d7f6c322 feat: add preliminary support for pep723 2024-01-03 13:09:19 -06:00
976f9fc0fa
feat: hide stacktraces 2024-01-02 12:40:46 -06:00
6700178d68
chore: ensure directories for dev-install 2024-01-02 12:26:10 -06:00
b378d47253
chore: update pre-commit to only use ruff 2024-01-02 12:25:09 -06:00
5c769fc905
fix: generate bin directory first 2024-01-02 12:22:47 -06:00
96eb08af6a
chore: some cleanup 2023-12-28 08:52:46 -06:00
726cef3092
build: why make when we can do 2023-12-28 08:27:03 -06:00
8c8bd616af
fix: typo on run example 2023-12-18 12:28:18 -06:00
3673e4940c
docs: furo -> shibuya 2023-12-18 12:27:44 -06:00
72ca25c302
fix(#15): use a username for semi-ephemeral 2023-12-11 13:17:02 -06:00
153d9fe774
fix: don't make extra directory in ephemeral directory 2023-10-23 12:53:12 -05:00
873310d900
docs: update outdated command 2023-10-18 13:58:19 -05:00
29d9d965b3 build: task.mk by default 2023-10-18 09:30:37 -05:00
tripleee
ad544f59d9 Makefile: small portability and usability fixes
* clean: don't use Bash syntax
2023-10-18 09:30:37 -05:00
323a194ff0
fix: remove deprecated command from docs 2023-10-16 13:48:41 -05:00
64d55811a2
ci: add flow to release workflow 2023-10-07 16:46:40 -05:00
2bc512314b
bump 2023.1002 -> 2023.1003 2023-10-07 16:41:33 -05:00
8986a436c0
docs: update svg generation with new commands 2023-10-07 16:41:23 -05:00
e6741792ee
build: add back complete lock file 2023-10-07 14:26:46 -05:00
134f220b4f
ci: pull_request not pr 2023-10-07 14:24:03 -05:00
173411dc9e
docs: switch to sphinx and splitup readme 2023-10-07 14:20:56 -05:00
c05e2ae22a
ci: add pr to test 2023-10-07 11:07:21 -05:00
tripleee
7e5c5746ee README.md: typo fixes and formatting tweaks
Mainly punctuation and other minutiae
2023-10-06 12:24:11 -05:00
d665f7ac6b
docs: update main help screenshot 2023-10-02 12:37:06 -05:00
0e262cc0f9
refactor: consolidate cli commands 2023-09-28 13:36:30 -05:00
d71542f5d2
fix: don't skip args if non run command using run 2023-09-27 16:06:22 -05:00
7286e90a36
feat: add experimental support for windows 2023-09-27 15:53:27 -05:00
24787a746c
style: implict types fine and properly escape things 2023-09-27 14:15:10 -05:00
4b3d1fbea5
tests: add some basic integration tests for python api 2023-09-27 13:34:10 -05:00
da77259862
fix: accept args with -s to make shebangs work better 2023-09-27 12:42:32 -05:00
2e916cab5f
bump 2023.1001 -> 2023.1002 2023-09-26 15:13:07 -05:00
62c3270826
fix: don't move local scripts just add viv.py to the path 2023-09-26 15:13:00 -05:00
3fe4110e06
build: fix release regex 2023-09-21 14:50:46 -05:00
ac02151a51
chore: sorry mypy forgot you were there 2023-08-30 14:32:41 -05:00
fa04385997
chore: add issue template 2023-08-30 12:29:41 -05:00
cb7d90f0e1
fix: add padding to size in list 2023-08-30 12:00:42 -05:00
4cdfb4fc50
refactor: drop unnecessary dict for list 2023-08-30 11:42:32 -05:00
68a6f3155a
feat: add system flag to viv manage show 2023-08-30 11:15:25 -05:00
f91d6eb4b4
fix: actually set date value 2023-08-29 16:05:42 -05:00
36 changed files with 3488 additions and 915 deletions

32
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View file

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

37
.github/workflows/test.yml vendored Normal file
View file

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

View file

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

View file

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

View file

@ -1,38 +0,0 @@
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,20 +29,18 @@
Try before you buy!
```sh
python3 <(curl -fsSL viv.dayl.in/viv.py) run pycowsay -- "viv isn't venv\!"
python3 <(curl -fsSL viv.dayl.in/viv.py) run frogmouth -- gh daylinmorgan/viv
```
---
`Viv` is a standalone dependency-free `venv` creator [^1].
`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:
`Viv`'s uncompromising insistence on portability means that it will always,
only use the standard library and never exceed a single script.
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.
See the [documentation](https://viv.dayl.in) or the [examples](https://github.com/daylinmorgan/viv/tree/main/examples) to get started.
## Setup
@ -52,8 +50,9 @@ Run the below command to install `viv`.
python3 <(curl -fsSL viv.dayl.in/viv.py) manage install
```
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` you can customize this with `--src`.
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`;
you can customize this with `--src`.
```sh
export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/viv"
@ -65,34 +64,48 @@ export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/viv"
pip install viv
```
Why is this *not recommended*? Mainly, because `viv` is all about hacking your `sys.path`.
Placing it in it's own virtual environment or installing in a user site directory may complicate this endeavor.
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.
## 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.
As a cli:
```sh
viv run frogmouth -- gh daylinmorgan/viv
```
As a python module:
```python
__import__("viv").use("click")
```
As an app installer:
```sh
viv shim ruff
```
To remove all `vivenvs` you can use the below command:
```sh
viv cache remove $(viv list -q)
viv env remove $(viv list -q)
```
To remove `viv` all together you can use the included `purge` command:
To remove `viv` altogether 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"
@ -100,7 +113,7 @@ 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 requests -b python -- -c "import requests; print(requests.get('https://pypi.org/project/viv').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/)
@ -118,19 +131,19 @@ python3 <(curl -fsSL viv.dayl.in/viv.py) run \
## Bonus: use `viv` with just standalone snippet (37LOC)
`--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.
`--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 the below command.
`viv freeze` also supports `--standalone`
`viv freeze` also supports `--standalone`.
```sh
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

View file

@ -1,4 +1,4 @@
<svg class="rich-terminal shadow" viewBox="0 0 890.3333333333334 545.9333333333333" xmlns="http://www.w3.org/2000/svg">
<svg class="rich-terminal shadow" viewBox="0 0 890.3333333333334 521.5333333333333" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io & yartsu https://github.com/daylinmorgan/yartsu -->
<style>
@ -19,14 +19,14 @@
font-weight: 700;
}
.terminal-1538990771-matrix {
.terminal-4099597553-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.terminal-1538990771-title {
.terminal-4099597553-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
@ -36,102 +36,98 @@
-webkit-filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
filter: drop-shadow( 2px 5px 2px rgba(0, 0, 0, .7));
}
.terminal-1538990771-r1 { fill: #94e2d5;font-weight: bold }
.terminal-1538990771-r2 { fill: #c6d0f5 }
.terminal-1538990771-r3 { fill: #f5c2e7;font-weight: bold }
.terminal-1538990771-r4 { fill: #c6d0f5;font-weight: bold }
.terminal-1538990771-r5 { fill: #f9e2af;font-weight: bold }
.terminal-4099597553-r1 { fill: #94e2d5;font-weight: bold }
.terminal-4099597553-r2 { fill: #c6d0f5 }
.terminal-4099597553-r3 { fill: #f5c2e7;font-weight: bold }
.terminal-4099597553-r4 { fill: #c6d0f5;font-weight: bold }
.terminal-4099597553-r5 { fill: #f9e2af;font-weight: bold }
</style>
<defs>
<clipPath id="terminal-1538990771-clip-terminal">
<rect x="0" y="0" width="853.0" height="462.59999999999997" />
<clipPath id="terminal-4099597553-clip-terminal">
<rect x="0" y="0" width="853.0" height="438.2" />
</clipPath>
<clipPath id="terminal-1538990771-line-0">
<clipPath id="terminal-4099597553-line-0">
<rect x="0" y="1.5" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-1">
<clipPath id="terminal-4099597553-line-1">
<rect x="0" y="25.9" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-2">
<clipPath id="terminal-4099597553-line-2">
<rect x="0" y="50.3" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-3">
<clipPath id="terminal-4099597553-line-3">
<rect x="0" y="74.7" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-4">
<clipPath id="terminal-4099597553-line-4">
<rect x="0" y="99.1" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-5">
<clipPath id="terminal-4099597553-line-5">
<rect x="0" y="123.5" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-6">
<clipPath id="terminal-4099597553-line-6">
<rect x="0" y="147.9" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-7">
<clipPath id="terminal-4099597553-line-7">
<rect x="0" y="172.3" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-8">
<clipPath id="terminal-4099597553-line-8">
<rect x="0" y="196.7" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-9">
<clipPath id="terminal-4099597553-line-9">
<rect x="0" y="221.1" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-10">
<clipPath id="terminal-4099597553-line-10">
<rect x="0" y="245.5" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-11">
<clipPath id="terminal-4099597553-line-11">
<rect x="0" y="269.9" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-12">
<clipPath id="terminal-4099597553-line-12">
<rect x="0" y="294.3" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-13">
<clipPath id="terminal-4099597553-line-13">
<rect x="0" y="318.7" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-14">
<clipPath id="terminal-4099597553-line-14">
<rect x="0" y="343.1" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-15">
<clipPath id="terminal-4099597553-line-15">
<rect x="0" y="367.5" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-16">
<clipPath id="terminal-4099597553-line-16">
<rect x="0" y="391.9" width="854" height="24.65"/>
</clipPath>
<clipPath id="terminal-1538990771-line-17">
<rect x="0" y="416.3" width="854" height="24.65"/>
</clipPath>
</defs>
<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>
<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>
<g transform="translate(32,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(18.166666666666664, 41) scale(.95)" clip-path="url(#terminal-1538990771-clip-terminal)">
<g transform="translate(18.166666666666664, 41) scale(.95)" clip-path="url(#terminal-4099597553-clip-terminal)">
<g class="terminal-1538990771-matrix">
<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-1538990771-r2" x="854" y="44.4" textLength="12.2" clip-path="url(#terminal-1538990771-line-1)">
</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-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-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-1538990771-r2" x="854" y="142" textLength="12.2" clip-path="url(#terminal-1538990771-line-5)">
</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-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-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-1538990771-r2" x="854" y="239.6" textLength="12.2" clip-path="url(#terminal-1538990771-line-9)">
</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-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-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-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-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-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-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-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)">
<g class="terminal-4099597553-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><text class="terminal-4099597553-r2" x="854" y="44.4" textLength="12.2" clip-path="url(#terminal-4099597553-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-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-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-4099597553-r2" x="854" y="142" textLength="12.2" clip-path="url(#terminal-4099597553-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-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-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-4099597553-r2" x="854" y="239.6" textLength="12.2" clip-path="url(#terminal-4099597553-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-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-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-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-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-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-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-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>
</g>
</g>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

23
docs/conf.py Normal file
View file

@ -0,0 +1,23 @@
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"},
],
}

45
docs/configuration.md Normal file
View file

@ -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` 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

40
docs/index.md Normal file
View file

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

56
docs/installation.md Normal file
View file

@ -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.

202
docs/usage.md Normal file
View file

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

31
docs/vs-others.md Normal file
View file

@ -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
```

View file

@ -7,6 +7,7 @@ it will generate a new vivenv.
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.
"""
__import__("viv").use("numpy", "plotext", track_exe=True) # noqa
import numpy as np

View file

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

View file

@ -1,17 +0,0 @@
#!/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])

15
examples/pep723.py Executable file
View file

@ -0,0 +1,15 @@
#!/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])

17
examples/pep723_run.py Executable file
View file

@ -0,0 +1,17 @@
#!/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 Executable file → Normal file
View file

View file

@ -2,14 +2,13 @@
"""
Embed the viv.py on the sys.path at runtime rather than using PYTHONPATH
"""
import sys
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
from difflib import unified_diff

View file

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

882
pdm.lock

File diff suppressed because it is too large Load diff

View file

@ -21,18 +21,28 @@ 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 = [
"mkdocs-material>=9.1.15",
"yartsu"
"sphinx",
"sphinx-autobuild",
"sphinx-copybutton",
"myst-parser",
"shibuya",
"yartsu",
]
test = [
"pytest",
"sampleproject"
]
[tool.ruff]
[tool.ruff.lint]
select = ["E","F","I"]
ignore = ["E402"]

View file

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

View file

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

222
scripts/vendor.py Executable file
View file

@ -0,0 +1,222 @@
#!/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 # noqa
from .viv import __version__, use, main, run # noqa

File diff suppressed because it is too large Load diff

45
task Executable file
View file

@ -0,0 +1,45 @@
#!/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 "$?"

12
tests/conftest.py Normal file
View file

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

52
tests/test_readers.py Normal file
View file

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

17
tests/test_use.py Normal file
View file

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

17
tests/test_vendored.py Normal file
View file

@ -0,0 +1,17 @@
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"
]
"""
)