Compare commits

...

13 commits

15 changed files with 687 additions and 178 deletions

27
.github/workflows/docs.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: GitHub Pages
on:
push:
tags: ["v*.*.*"]
workflow_dispatch:
jobs:
deploy-docs:
runs-on: ubuntu-22.04
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v3
- name: Add Source to Docs
run: cp ./src/viv/viv.py docs/viv.py
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# publish_dir: ./site
publish_dir: ./docs

View file

@ -10,26 +10,20 @@ types: ## run mypy
bump-version: ## update version and tag commit
@echo "bumping to version => $(VERSION)"
@sed -i 's/__version__ = ".*"/__version__ = "$(VERSION)"/g' src/viv/viv.py
@sed 's/--branch .* g/--branch $(VERSION) g/g' README.md
@git add src/viv/viv.py && git commit -m "chore: bump version"
@sed -i 's/install -r .*/install -r v$(VERSION)/g' README.md
@git add src/viv/viv.py README.md && git commit -m "chore: bump version" --no-verify
@git tag v$(VERSION)
venv: ## generate environment
pdm install
install: ## symlink to $PREFIX
ln -sf $(shell pwd)/src/viv/viv.py $(PREFIX)/viv
# TAPES = demo freeze list-info-remove
# GIFS := $(foreach n, $(TAPES), docs/$(n).gif)
# docs: $(GIFS) ## generate usage examples
uninstall: ## delete $(PREFIX)/viv
rm $(PREFIX)/viv
TAPES = demo freeze list-info-remove
GIFS := $(foreach n, $(TAPES), docs/$(n).gif)
docs: $(GIFS) ## generate usage examples
docs/%.gif: docs/%.tape
viv rm $$(viv l -q)
cd docs; vhs < $*.tape
# docs/%.gif: docs/%.tape
# viv rm $$(viv l -q)
# cd docs; vhs < $*.tape
clean: ## remove build artifacts
rm -rf {build,dist}

View file

@ -1,9 +1,8 @@
# Viv
# viv
<!-- PROJECT DEMO -->
<div align="center">
<a href="https://github.com/daylinmorgan/viv">
<img src="https://raw.githubusercontent.com/daylinmorgan/viv/main/docs/demo.gif" alt="Logo" width=600 >
<img src="https://raw.githubusercontent.com/daylinmorgan/viv/main/assets/logo.svg" alt="Logo" width=500 >
</a>
<p align="center">
viv isn't venv
@ -11,8 +10,6 @@
</div>
<br />
See [usage](https://github.com/daylinmorgan/viv/blob/main/docs/usage.md) for more demo gifs.
---
Python is a great choice to quickly prototype or accomplish small tasks in scripts.
@ -26,27 +23,22 @@ prior to loading of any of the external modules.
These `venvs` can be identified by name or by their specification.
In any case they will be re-used across scripts (and generated on-demand, if needed).
**Importantly**, `viv` will remove your user site directory (`python -m 'import site;print(site.USER_SITE)'`),
to ensure the script isn't using anything outside the standard library and the `viv`-managed `venv`.
**Importantly**, `viv` will also remove your user site directory.
(view with: `python -m 'import site;print(site.USER_SITE)'`).
## Setup
### Manual (Recommended)
Start by cloning the repo and symlinking the script for access to the CLI.
By default it will symlink `./src/viv/viv.py` to `~/bin/viv`.
You can set `PREFIX` to symlink to a different location.
Run the below command to install `viv`.
```sh
git clone --depth 1 --branch v22.12a3 git@github.com:daylinmorgan/viv.git ~/.viv
cd ~/.viv
make install # or PREFIX=~/.local/bin make install
python3 <(curl -fsSL gh.dayl.in/viv/viv.py) manage install -r v23.5a1
```
Place this directory on the python path in your rc file.
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`.
```sh
export PYTHONPATH="$PYTHONPATH:$HOME/.viv/src"
export PYTHONPATH="$PYTHONPATH:$HOME/.local/share/viv"
```
Advanced users may recognize that principally,
@ -72,7 +64,7 @@ to automate `vivenv` creation and installation of dependencies.
__import__("viv").use("click")
```
To remove all `vivenvs`:
To remove all `vivenvs` you can use the below command:
```sh
viv remove $(viv list -q)
@ -80,9 +72,7 @@ viv remove $(viv list -q)
# Standalone Viv
*Requires* `python>=3.8`
Supposing you want to increase the portability of your script while still employing `viv`.
Supposing you want to increase the portability of your script while still employing the principles of `viv`.
The below function can be freely pasted at the top of your scripts and requires
no modification of your PYTHONPATH or import of additional modules (including downloading/installing `viv`).
@ -117,8 +107,6 @@ def _viv_use(*pkgs: str, track_exe: bool = False, name: str = "") -> None:
_viv_use("markdown-it-py==2.2.0", "mdurl==0.1.2", "Pygments==2.14.0", "rich==13.3.2") # noqa
# fmt: on
# >>>>> code golfed with <3
```
## Alternatives

323
assets/logo.svg Normal file
View file

@ -0,0 +1,323 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
id="svg2233"
viewBox="0 0 286.80808 184.19898"
version="1.0"
xml:space="preserve"
width="286.80807"
height="184.19897"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
id="defs2235"><linearGradient
id="linearGradient7463"><stop
id="stop7465"
style="stop-color:#d1e3e2;stop-opacity:.54680"
offset="0" /><stop
id="stop3853"
style="stop-color:#eeeeee;stop-opacity:.61569"
offset=".5" /><stop
id="stop7467"
style="stop-color:#d1e3e2;stop-opacity:.54510"
offset="1" /></linearGradient><filter
id="filter3662"
x="-0.0066908929"
y="-0.044310786"
width="1.0133818"
height="1.0886216"><feGaussianBlur
id="feGaussianBlur3664"
stdDeviation="0.98997704" /></filter><radialGradient
id="radialGradient2603"
fx="272.03"
fy="553.58002"
gradientUnits="userSpaceOnUse"
cy="707.21997"
cx="271.98999"
gradientTransform="matrix(0.56638235,-0.00114952,1.732559e-4,0.0853702,23.42315,270.93592)"
r="210"><stop
id="stop11688"
style="stop-color:#000000;stop-opacity:.48768"
offset="0" /><stop
id="stop11690"
style="stop-color:#000000;stop-opacity:0"
offset="1" /></radialGradient><linearGradient
id="linearGradient2605"
y2="782.23999"
gradientUnits="userSpaceOnUse"
x2="287.59"
gradientTransform="matrix(1.157,0,0,1.2361,-49.276,-172.53)"
y1="739.82001"
x1="287.59"><stop
id="stop3673"
style="stop-color:#000000;stop-opacity:.10345"
offset="0" /><stop
id="stop3675"
style="stop-color:#000000;stop-opacity:0"
offset="1" /></linearGradient><linearGradient
id="linearGradient2669"
y2="775.28003"
gradientUnits="userSpaceOnUse"
x2="255.53"
gradientTransform="matrix(0.49986408,0,0,0.49986408,22.394217,-45.390032)"
y1="488.28"
x1="292.42001"><stop
id="stop3879"
style="stop-color:#ffffff;stop-opacity:.43429"
offset="0" /><stop
id="stop3881"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" /></linearGradient><linearGradient
id="linearGradient2672"
y2="460.76001"
xlink:href="#linearGradient7463"
gradientUnits="userSpaceOnUse"
x2="128.5"
gradientTransform="matrix(0.49986408,0,0,0.49986408,22.394217,-45.318973)"
y1="405.76001"
x1="440.75" /><linearGradient
id="linearGradient2675"
y2="226.81"
xlink:href="#linearGradient7463"
gradientUnits="userSpaceOnUse"
x2="371.47"
gradientTransform="matrix(-0.68684145,0,0,-0.68684145,349.40033,489.25029)"
y1="401.95999"
x1="181.42" /><linearGradient
id="linearGradient2678"
y2="460.76001"
xlink:href="#linearGradient7463"
gradientUnits="userSpaceOnUse"
x2="128.5"
gradientTransform="matrix(0.49986408,0,0,0.49986408,19.707478,97.509558)"
y1="405.76001"
x1="440.75" /><linearGradient
id="linearGradient2683"
y2="226.81"
xlink:href="#linearGradient7463"
gradientUnits="userSpaceOnUse"
x2="371.47"
gradientTransform="matrix(-0.50539246,0.71869007,0,-0.68010507,280.36657,219.24055)"
y1="401.95999"
x1="181.42" /><linearGradient
id="linearGradient2686"
y2="226.81"
xlink:href="#linearGradient7463"
gradientUnits="userSpaceOnUse"
x2="371.47"
gradientTransform="matrix(-0.13654685,-0.11142041,0,-0.68684145,286.98927,501.88457)"
y1="401.95999"
x1="181.42" /><linearGradient
id="linearGradient2689"
y2="226.81"
xlink:href="#linearGradient7463"
gradientUnits="userSpaceOnUse"
x2="371.47"
gradientTransform="matrix(-0.68684145,0,0,-0.68684145,364.94802,462.39001)"
y1="401.95999"
x1="181.42" /><linearGradient
id="linearGradient2795"><stop
style="stop-color:#b8b8b8;stop-opacity:0.49803922"
offset="0"
id="stop2797" /><stop
style="stop-color:#7f7f7f;stop-opacity:0"
offset="1"
id="stop2799" /></linearGradient><linearGradient
id="linearGradient2787"><stop
style="stop-color:#7f7f7f;stop-opacity:0.5"
offset="0"
id="stop2789" /><stop
style="stop-color:#7f7f7f;stop-opacity:0"
offset="1"
id="stop2791" /></linearGradient><linearGradient
id="linearGradient3676"><stop
style="stop-color:#b2b2b2;stop-opacity:0.5"
offset="0"
id="stop3678" /><stop
style="stop-color:#b3b3b3;stop-opacity:0"
offset="1"
id="stop3680" /></linearGradient><linearGradient
id="linearGradient3236"><stop
style="stop-color:#f4f4f4;stop-opacity:1"
offset="0"
id="stop3244" /><stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop3240" /></linearGradient><linearGradient
id="linearGradient4671"><stop
style="stop-color:#ffd43b;stop-opacity:1"
offset="0"
id="stop4673" /><stop
style="stop-color:#ffe873;stop-opacity:1"
offset="1"
id="stop4675" /></linearGradient><linearGradient
id="linearGradient4689"><stop
style="stop-color:#5a9fd4;stop-opacity:1"
offset="0"
id="stop4691" /><stop
style="stop-color:#306998;stop-opacity:1"
offset="1"
id="stop4693" /></linearGradient><linearGradient
x1="224.23996"
y1="144.75717"
x2="-65.308502"
y2="144.75717"
id="linearGradient2987"
xlink:href="#linearGradient4671"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(100.2702,99.61116)" /><linearGradient
x1="172.94208"
y1="77.475983"
x2="26.670298"
y2="76.313133"
id="linearGradient2990"
xlink:href="#linearGradient4689"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(100.2702,99.61116)" /><linearGradient
x1="224.23996"
y1="144.75717"
x2="-65.308502"
y2="144.75717"
id="linearGradient2255"
xlink:href="#linearGradient4671"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.562541,0,0,0.567972,-11.5974,-7.60954)" /><linearGradient
x1="172.94208"
y1="76.176224"
x2="26.670298"
y2="76.313133"
id="linearGradient2258"
xlink:href="#linearGradient4689"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.562541,0,0,0.567972,-11.5974,-7.60954)" /><radialGradient
cx="61.518883"
cy="132.28575"
r="29.036913"
fx="61.518883"
fy="132.28575"
id="radialGradient2801"
xlink:href="#linearGradient2795"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.177966,0,108.7434)" /><linearGradient
x1="150.96111"
y1="192.35176"
x2="112.03144"
y2="137.27299"
id="linearGradient1475"
xlink:href="#linearGradient4671"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" /><linearGradient
x1="26.648937"
y1="20.603781"
x2="135.66525"
y2="114.39767"
id="linearGradient1478"
xlink:href="#linearGradient4689"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.562541,0,0,0.567972,-9.399749,-5.305317)" /><radialGradient
cx="61.518883"
cy="132.28575"
r="29.036913"
fx="61.518883"
fy="132.28575"
id="radialGradient1480"
xlink:href="#linearGradient2795"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.382716e-8,-0.296405,1.43676,4.683673e-7,-128.544,150.5202)" /></defs><g
id="layer4"
transform="translate(-34.19,-165.32103)"><path
id="path1648"
style="display:none;fill:none;stroke:#ffffff;stroke-width:1px"
d="m 255.17,667.94 c 105.62,-29.82 164.49,-18.7 304.85,-6.95 82.52,6.91 180.3,-11.42 237.14,-8.54 95.12,4.81 189.98,28.25 321.14,20.54" /><path
id="path11576"
style="fill:url(#radialGradient2603);fill-rule:evenodd;stroke-width:0.710589"
d="M 177.59404,304.59654 C 98.434384,304.59654 34.19,318.7799 34.19,329.71587 34.1914,340.64474 98.434384,349.52 177.59404,349.52 c 79.15965,0 143.40404,-8.87526 143.40404,-19.80413 0,-10.93597 -64.24439,-25.11933 -143.40404,-25.11933 z m -60.86198,3.58848 h 115.25759 c 1.96834,0.22028 5.15888,0.90955 6.95667,2.70734 l 24.96301,19.31382 c 1.83332,1.83332 0.98061,4.78227 -1.91859,4.78227 H 97.503511 c -2.657604,0 -4.37723,-2.55812 -3.538734,-4.12142 L 111.16104,311.0629 c 1.5633,-1.86174 2.83525,-2.74998 5.57102,-2.87788 z" /><path
id="path3669"
style="fill:url(#linearGradient2605);filter:url(#filter3662)"
transform="matrix(0.49986408,0,0,0.49986408,22.394217,-45.390034)"
d="m 169.09,708.78 c -6.32,0.32 -9.29,2.55 -12.9,7.16 l -39.85,48.94 c -1.93,3.85 2.1,10.21 8.25,10.21 h 380.72 c 6.71,0 8.64,-7.31 4.41,-11.84 l -57.78,-47.78 c -4.16,-4.46 -11.54,-6.14 -16.1,-6.69 h -9.75 c 2.65,0.85 5.26,2.09 7.13,3.97 l 49.94,38.66 c 3.66,3.66 1.98,9.56 -3.82,9.56 H 150.28 c -5.32,0 -8.79,-5.13 -7.12,-8.25 l 34.43,-39.6 c 1.66,-1.97 3.15,-3.39 5,-4.34 z" /><path
id="path3660"
style="fill:#000000;fill-opacity:0.068571;filter:url(#filter3662)"
transform="matrix(0.49986408,0,0,0.49986408,22.394217,-45.390034)"
d="m 176.84,714 c -1.54,0.96 -2.86,2.27 -4.31,4 l -35.72,41.06 c -1.73,3.24 1.86,8.57 7.38,8.56 H 485.5 c 6.01,0 7.77,-6.1 3.97,-9.9 l -4.66,-3.6 c 1.04,3.31 -0.96,6.85 -5.47,6.85 H 150.28 c -5.32,0 -8.79,-5.13 -7.12,-8.25 z" /><path
id="path3873"
d="m 266.56693,326.90194 h -6.3882 l 5.96185,-130.62764 h 6.3882 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3871"
d="M 99.500268,329.10477 H 93.119175 V 196.01138 h 6.381093 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3867"
d="m 114.54344,310.55128 c -2.65049,-0.0995 -4.74673,-0.52584 -5.39337,-4.1001 l -15.83193,19.39909 c -0.390824,6.17502 0.483201,8.12914 4.213795,7.54646 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3869"
d="m 234.71832,310.56549 c 2.60075,-0.0924 5.30099,-2.416 5.08782,-5.50707 l 26.83185,20.10968 c -0.44767,6.8714 -1.76937,9.05291 -3.90824,8.73315 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3863"
d="m 111.01892,169.85601 c 0.0782,-1.26911 -2.01097,-1.64359 -2.94894,-0.0185 L 95.002237,190.6109 c -1.009037,1.65568 -1.819109,2.40179 -1.819109,5.72735 l 3.389511,-2.99158 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3865"
d="m 241.19178,170.08553 c -0.46898,-1.181 1.98966,-1.58248 3.34688,-0.73901 l 25.21882,20.3193 c 1.48513,1.25774 2.48706,2.8992 2.59365,6.30293 l -3.78034,-2.6434 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3857"
d="m 115.53827,306.08878 h -6.38109 V 170.07487 h 6.38109 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3859"
d="m 239.8843,305.9893 c 0,1.94701 -2.54391,4.76805 -5.89789,4.76805 H 114.72109 c -3.26871,0 -5.53549,-1.06588 -5.53549,-4.76805 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3861"
d="m 239.95536,306.08878 h -6.38109 V 170.07487 h 6.38109 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3855"
d="m 245.44822,170.0834 c -1.81201,-1.53914 -6.69376,-4.76237 -10.04063,-4.76237 H 114.76373 c -3.07685,0 -5.08782,2.03939 -6.85719,4.76237 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3849"
d="M 230.37661,307.51706 H 117.73399 c -2.37337,0 -5.09493,-3.05553 -5.09493,-5.22283 V 171.24024 c 0,-1.09147 2.13888,-3.62046 3.69507,-3.61832 l 116.2311,0.16556 c 2.0465,0 3.3753,2.2341 3.3753,3.62969 V 303.2386 c 0,2.27389 -1.9257,4.24933 -5.56392,4.27775 z"
style="fill:url(#linearGradient2689);stroke-width:0.710589" /><path
id="path3845"
style="fill:url(#linearGradient2686);stroke-width:0.710589"
d="m 262.15417,327.49884 -24.10319,-19.1646 c -1.208,-0.95929 -0.81718,-5.03808 -0.81718,-5.03808 V 172.29191 c 0,-3.14365 5.68472,-4.25501 7.5962,-2.71232 l 25.33962,20.47705 c 1.52777,1.23643 2.36626,4.13563 2.36626,6.90693 l -5.80551,127.2026 c 0,5.40759 -2.23125,5.63498 -4.5762,3.33267 z" /><path
id="path3837"
style="fill:url(#linearGradient2683);stroke-width:0.710589"
d="m 108.77346,312.39881 -12.001856,12.85456 c -2.138874,2.2881 -3.496099,8.60524 -3.496099,3.83008 l -0.1208,-131.33112 c -0.0071,-0.93798 0.774542,-4.15695 1.840426,-5.83394 l 13.259599,-20.81317 c 0.67506,-1.05238 3.22607,-1.03959 3.22607,1.69121 v 133.21418 c 0,2.24547 -1.71252,5.11625 -2.70734,6.3882 z" /><path
id="path7416"
d="m 272.53588,196.45195 c 0,-2.11045 -0.87402,-4.87465 -2.38758,-6.3882 H 95.371743 c -1.094307,1.49934 -2.238356,3.98641 -2.238356,6.3882 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3835"
d="m 265.72844,329.05503 c 0,3.82297 -1.50645,5.94763 -3.6098,5.94763 H 97.801959 c -3.403723,0 -4.668572,-2.62208 -4.668572,-6.0329 z"
style="fill:#9ab7c1;stroke-width:0.710589" /><path
id="path3831"
d="m 116.73917,308.18502 h 115.25048 c 1.96834,0.22028 5.15888,0.90245 6.95667,2.70024 l 24.96301,19.32803 c 1.82621,1.83332 0.98061,4.78226 -1.91149,4.78226 H 97.510617 c -2.66471,0 -4.391442,-2.57233 -3.552946,-4.12852 l 17.203369,-19.78991 c 1.5633,-1.86885 2.84236,-2.7642 5.57813,-2.8921 z"
style="fill:url(#linearGradient2678);stroke-width:0.710589" /><path
id="rect7391"
d="M 262.18259,332.01108 H 98.56229 c -2.373369,0 -3.474782,-0.68927 -3.474782,-2.84947 V 196.72908 c 0,-1.09431 0.696377,-2.24547 2.259674,-2.24547 l 171.657068,0.16344 c 0.98062,0.007 2.50128,0.3624 2.50128,1.75516 l -5.7842,131.33112 c 0,2.26678 0.10659,4.24222 -3.53874,4.27775 z"
style="fill:url(#linearGradient2675);stroke-width:0.710589" /><g
id="g2303"
transform="matrix(1.0141096,0,0,1.0141096,120.25928,195.81719)"><path
id="path1948"
style="fill:url(#linearGradient1478);fill-opacity:1"
d="m 60.510156,6.3979729 c -4.583653,0.021298 -8.960939,0.4122177 -12.8125,1.09375 C 36.35144,9.4962267 34.291407,13.691825 34.291406,21.429223 v 10.21875 h 26.8125 v 3.40625 h -26.8125 -10.0625 c -7.792459,0 -14.6157592,4.683717 -16.7500002,13.59375 -2.46182,10.212966 -2.5710151,16.586023 0,27.25 1.9059283,7.937852 6.4575432,13.593748 14.2500002,13.59375 h 9.21875 v -12.25 c 0,-8.849902 7.657144,-16.656248 16.75,-16.65625 h 26.78125 c 7.454951,0 13.406253,-6.138164 13.40625,-13.625 v -25.53125 c 0,-7.266339 -6.12998,-12.7247775 -13.40625,-13.9375001 -4.605987,-0.7667253 -9.385097,-1.1150483 -13.96875,-1.09375 z m -14.5,8.2187501 c 2.769547,0 5.03125,2.298646 5.03125,5.125 -2e-6,2.816336 -2.261703,5.09375 -5.03125,5.09375 -2.779476,-1e-6 -5.03125,-2.277415 -5.03125,-5.09375 -1e-6,-2.826353 2.251774,-5.125 5.03125,-5.125 z" /><path
id="path1950"
style="fill:url(#linearGradient1475);fill-opacity:1"
d="m 91.228906,35.054223 v 11.90625 c 0,9.230755 -7.825895,16.999999 -16.75,17 h -26.78125 c -7.335833,0 -13.406249,6.278483 -13.40625,13.625 v 25.531247 c 0,7.26634 6.318588,11.54032 13.40625,13.625 8.487331,2.49561 16.626237,2.94663 26.78125,0 6.750155,-1.95439 13.406253,-5.88761 13.40625,-13.625 V 92.897973 h -26.78125 v -3.40625 h 26.78125 13.406254 c 7.79246,0 10.69625,-5.435408 13.40624,-13.59375 2.79933,-8.398886 2.68022,-16.475776 0,-27.25 -1.92578,-7.757441 -5.60387,-13.59375 -13.40624,-13.59375 z m -15.0625,64.65625 c 2.779478,3e-6 5.03125,2.277417 5.03125,5.093747 -2e-6,2.82635 -2.251775,5.125 -5.03125,5.125 -2.76955,0 -5.03125,-2.29865 -5.03125,-5.125 2e-6,-2.81633 2.261697,-5.093747 5.03125,-5.093747 z" /><path
id="path1894"
style="opacity:0.44382;fill:url(#radialGradient1480);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(0.73406,0,0,0.809524,16.24958,27.00935)"
d="m 110.46717,132.28575 a 48.948285,8.6066792 0 1 1 -97.896571,0 48.948285,8.6066792 0 1 1 97.896571,0 z" /></g><path
id="rect7393"
style="fill:url(#linearGradient2672);stroke-width:0.710589"
d="m 115.10481,165.35798 c -2.95605,0 -4.89596,1.63364 -5.74867,2.88997 l -13.749902,21.88686 c -0.753225,1.30748 -0.09238,2.24546 1.129837,2.24546 l 170.733305,0.24871 c 2.26678,-0.17765 3.29003,-0.86692 0.0426,-3.33977 l -24.7285,-20.29159 c -1.7978,-1.80135 -4.60462,-3.63964 -8.10783,-3.63964 z" /><path
id="path3875"
d="M 262.18259,332.01108 H 98.56229 c -2.373369,0 -3.474782,-0.68927 -3.474782,-2.84947 v -76.63706 c 48.163742,12.18661 119.151622,17.80737 173.504602,-1.47802 l -2.87078,76.6868 c 0,2.26678 0.10659,4.24222 -3.53874,4.27775 z"
style="fill:url(#linearGradient2669);stroke-width:0.710589" /></g><metadata
id="metadata49"><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><cc:license
rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" /><dc:publisher><cc:Agent
rdf:about="http://openclipart.org/"><dc:title>Openclipart</dc:title></cc:Agent></dc:publisher><dc:date>2007-10-31T17:22:52</dc:date><dc:description>A strip of Poll box by BenBois</dc:description><dc:source>https://openclipart.org/detail/7782/transparent-cube-by-molumen</dc:source><dc:creator><cc:Agent><dc:title>molumen</dc:title></cc:Agent></dc:creator><dc:subject><rdf:Bag><rdf:li>box</rdf:li><rdf:li>cube</rdf:li><rdf:li>glass</rdf:li><rdf:li>plexiglass</rdf:li><rdf:li>remix</rdf:li><rdf:li>shape</rdf:li><rdf:li>transparent</rdf:li></rdf:Bag></dc:subject></cc:Work><cc:License
rdf:about="http://creativecommons.org/publicdomain/zero/1.0/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /></cc:License></rdf:RDF></metadata></svg>

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 757 KiB

View file

@ -1,11 +0,0 @@
#!/usr/bin/env python3
__import__("viv").use("pyfiglet==0.8.post1") # noqa
from pyfiglet import Figlet
if __name__ == "__main__":
f = Figlet(font="slant")
figtxt = f.renderText("viv").splitlines()
figtxt[-2] += " isn't venv!"
print("\n".join(figtxt))

View file

@ -1,23 +0,0 @@
Set Height 750
Set Width 1500
Set Theme "Catppuccin Mocha"
Output ./demo.gif
Type "viv list"
Enter
Sleep 2s
Type "python ../examples/cli.py --help"
Enter
Sleep 10s
Type "viv list"
Enter
Sleep 3s
Type "viv info 841"
Enter
Sleep 3s
Type "python ../examples/cli.py hello 'prospective viv user!'"
Enter
Sleep 2s
Type "python ../examples/cli.py goodbye 'prospective viv user!'"
Enter
Sleep 2s

View file

@ -1,23 +0,0 @@
Set Height 750
Set Width 1500
Set Theme "Catppuccin Mocha"
Output ./freeze.gif
Type "viv freeze --help"
Enter
Sleep 2s
Type "viv list"
Enter
Sleep 2s
Type "viv freeze requests bs4"
Enter
Sleep 7s
Type "viv list"
Enter
Sleep 3s
Type "viv freeze requests bs4 --keep --path relative"
Enter
Sleep 7s
Type "viv list"
Enter
Sleep 5s

9
docs/index.html Normal file
View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<link rel="canonical" href="https://github.com/daylinmorgan/viv"/>
<meta name="robots" content="noindex">
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta http-equiv="refresh" content="0; url=https://github.com/daylinmorgan/viv"/>
</head>
</html>

View file

@ -1,30 +0,0 @@
Set Height 750
Set Width 1500
Set Theme "Catppuccin Mocha"
Output ./list-info-remove.gif
Type "cat demo.py"
Enter
Sleep 500ms
Type "python demo.py"
Enter
Sleep 3s
Type "viv list -h"
Enter
Sleep 500ms
Type "viv list -q"
Enter
Sleep 1s
Type "viv info -h"
Enter
Sleep 500ms
Type "viv info "
Sleep 1s
Type "f8"
Enter
Sleep 1s
Type "viv remove -h"
Enter
Type "viv rm f8"
Enter
Sleep 2s

View file

@ -1,18 +0,0 @@
# Usage
*NOTE*: these demo gif's are a work in progress. If you'd like to see them you can run `vhs` locally with `make docs`.
<div align="center">
# Demo
<img src="https://raw.githubusercontent.com/daylinmorgan/viv/main/docs/demo.gif" alt="demo" width=600 >
# Freeze
<img src="https://raw.githubusercontent.com/daylinmorgan/viv/main/docs/freeze.gif" alt="demo" width=600 >
# List | Info | Remove
<img src="https://raw.githubusercontent.com/daylinmorgan/viv/main/docs/list-info-remove.gif" alt="demo" width=600 >
</div>

View file

@ -8,10 +8,9 @@ A possible cli signature
viv shim black -o ~/bin/black
"""
import subprocess
import sys
sys.path.append("/home/daylin/.viv/src") # noqa
import subprocess
import viv
if __name__ == "__main__":

View file

@ -17,7 +17,7 @@ homepage = "https://github.com/daylinmorgan/viv"
repository = "https://github.com/daylinmorgan/viv"
[project.scripts]
viv = "viv.viv:main"
viv = "viv:main"
[tool.pdm]
version = { source = "scm" }
@ -29,6 +29,7 @@ dev = [
]
[tool.ruff]
select = ["E","F","I"]
ignore = ["E402"]
[tool.mypy]

View file

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

View file

@ -22,14 +22,15 @@ import tempfile
import threading
import time
import venv
from argparse import SUPPRESS, Action
from argparse import ArgumentParser as StdArgParser
from argparse import (
SUPPRESS,
Action,
HelpFormatter,
Namespace,
RawDescriptionHelpFormatter,
_SubParsersAction,
)
from argparse import ArgumentParser as StdArgParser
from dataclasses import dataclass
from datetime import datetime
from itertools import zip_longest
@ -39,6 +40,7 @@ from types import TracebackType
from typing import (
Any,
Dict,
Generator,
List,
NoReturn,
Optional,
@ -46,10 +48,11 @@ from typing import (
TextIO,
Tuple,
Type,
Generator,
)
from urllib.error import HTTPError
from urllib.request import urlopen
__version__ = "22.12a3-41-g5c40210-dev"
__version__ = "23.5a1-3-g3a85fe4-dev"
@dataclass
@ -59,9 +62,21 @@ class Config:
venvcache: Path = (
Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache")) / "viv" / "venvs"
)
srccache: Path = (
Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache")) / "viv" / "src"
)
share: Path = (
Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share")) / "viv"
)
def __post_init__(self) -> None:
self.venvcache.mkdir(parents=True, exist_ok=True)
self.srccache.mkdir(
parents=True,
exist_ok=True,
)
self.share.mkdir(parents=True, exist_ok=True)
self.srcdefault = self.share / "viv.py"
c = Config()
@ -235,6 +250,9 @@ class Ansi:
else:
return (row,)
def viv_preamble(self, style: str = "magenta", sep: str = "::") -> str:
return f"{self.cyan}Viv{self.end}{self.__dict__[style]}{sep}{self.end}"
def table(
self, rows: Tuple[Tuple[str, Sequence[str]], ...], header_style: str = "cyan"
) -> None:
@ -299,12 +317,27 @@ def echo(
msg: str, style: str = "magenta", newline: bool = True, fd: TextIO = sys.stderr
) -> None:
"""output general message to stdout"""
output = f"{a.cyan}Viv{a.end}{a.__dict__[style]}::{a.end} {msg}"
output = f"{a.viv_preamble(style)} {msg}"
if newline:
output += "\n"
fd.write(output)
def confirm(question: str, context: str = "") -> bool:
sys.stderr.write(context)
sys.stderr.write(
a.viv_preamble(sep="?? ") + question + a.style(" (Y)es/(n)o: ", "yellow")
)
while True:
ans = input().strip().lower()
if ans in ("y", "yes"):
return True
elif ans in ("n", "no"):
return False
sys.stdout.write("Please select (Y)es or (n)o. ")
sys.stdout.write("\n")
def run(
command: List[str],
spinmsg: str = "",
@ -433,12 +466,14 @@ class ViVenv:
def dump_info(self, write: bool = False) -> None:
# TODO: include associated files in 'info'
# means it needs to be loaded first
# or keep a seperate file hash in c.share?
info = {
"created": str(datetime.today()),
"id": self.id,
"spec": self.spec,
"exe": self.exe,
}
# save metadata to json file
if write:
with (self.path / "viv-info.json").open("w") as f:
@ -448,7 +483,7 @@ class ViVenv:
a.table((("key", "value"), *((k, v) for k, v in info.items())))
def use(*packages: str, track_exe: bool = False, name: str = "") -> None:
def use(*packages: str, track_exe: bool = False, name: str = "") -> ViVenv:
"""create a vivenv and append to sys.path
Args:
@ -467,6 +502,7 @@ def use(*packages: str, track_exe: bool = False, name: str = "") -> None:
vivenv.dump_info(write=True)
modify_sys_path(vivenv.path)
return vivenv
def validate_spec(spec: Tuple[str, ...]) -> None:
@ -515,8 +551,7 @@ STANDALONE_TEMPLATE = r"""
# >>>>> code golfed with <3
""" # noqa
STANDALONE_TEMPLATE_USE = r"""
def _viv_use(*pkgs: str, track_exe: bool = False, name: str = "") -> None:
STANDALONE_TEMPLATE_USE = r"""def _viv_use(*pkgs: str, track_exe: bool = False, name: str = "") -> None:
i,s,m,e,spec=__import__,str,map,lambda x: True if x else False,[*pkgs]
if not {{*m(type,pkgs)}}=={{s}}: raise ValueError(f"spec: {{pkgs}} is invalid")
ge,sys,P,ew=i("os").getenv,i("sys"),i("pathlib").Path,i("sys").stderr.write
@ -535,9 +570,27 @@ def _viv_use(*pkgs: str, track_exe: bool = False, name: str = "") -> None:
i("json").dump({{"created":s(i("datetime").datetime.today()),"id":_id,"spec":spec,"exe":exe}},f)
sys.path = [p for p in (*sys.path,s(*(env/"lib").glob("py*/si*"))) if p!=i("site").USER_SITE]
_viv_use({spec})
"""[ # noqa
1:
]
""" # noqa
SHOW_TEMPLATE = f"""
{a.style('Version', 'bold')}: {{version}}
{a.style('CLI', 'bold')}: {{cli}}
{a.style('Running Source', 'bold')}: {{running_src}}
{a.style('Local Source', 'bold')}: {{local_src}}
"""
INSTALL_TEMPLATE = f"""
Install viv.py to {a.green}{{src_location}}{a.end}
Symlink {a.bold}{{src_location}}{a.end} to {a.bold}{{cli_location}}{a.end}
"""
UPDATE_TEMPLATE = f"""
Update source at {a.green}{{src_location}}{a.end}
Symlink {a.bold}{{src_location}}{a.end} to {a.bold}{{cli_location}}{a.end}
Version {a.bold}{{local_version}}{a.end} -> {a.bold}{{next_version}}{a.end}
"""
def noqa(txt: str) -> str:
@ -691,7 +744,7 @@ class CustomHelpFormatter(RawDescriptionHelpFormatter, HelpFormatter):
# determine the required width and the entry label
help_position = min(self._action_max_length + 2, self._max_help_position)
help_width = max(self._width - help_position, 11)
action_width = help_position - self._current_indent - 2
action_width = help_position - self._current_indent
action_header = self._format_action_invocation(action)
action_header_len = len(a.escape(action_header))
@ -763,7 +816,8 @@ class ArgumentParser(StdArgParser):
super().__init__(*args, **kwargs)
self.formatter_class = lambda prog: CustomHelpFormatter(
prog, max_help_position=35
prog,
max_help_position=35,
)
def error(self, message: str) -> NoReturn:
@ -776,18 +830,76 @@ class ArgumentParser(StdArgParser):
description = f"""
{a.tagline()}
{a.style('create/activate a vivenv','underline')}
from command line:
`{a.style("viv -h","bold")}`
within python script:
{a.style('__import__("viv").use("typer", "rich-click")','bold')}
to create/activate a vivenv:
- from command line: `{a.style("viv -h","bold")}`
- within python script: {a.style('__import__("viv").use("typer", "rich-click")','bold')}
"""
def fetch_source(reference: str) -> str:
try:
r = urlopen(
"https://raw.githubusercontent.com/daylinmorgan/viv/"
+ reference
+ "/src/viv/viv.py"
)
except HTTPError as e:
error(
"Issue updating viv see below:"
+ a.style("-> ", "red").join(["\n"] + repr(e).splitlines())
)
if "404" in repr(e):
echo("Please check your reference is valid.", style="red")
sys.exit(1)
src = r.read()
(hash := hashlib.sha256()).update(src)
sha256 = hash.hexdigest()
cached_src_file = c.srccache / f"{sha256}.py"
if not cached_src_file.is_file():
with cached_src_file.open("w") as f:
f.write(src.decode())
return sha256
def make_executable(path: Path) -> None:
"""apply an executable bit for all users with read access"""
mode = os.stat(path).st_mode
mode |= (mode & 0o444) >> 2 # copy R bits to X
os.chmod(path, mode)
class Viv:
def __init__(self) -> None:
self.vivenvs = get_venvs()
self._get_sources()
self.name = (
"viv" if self.local else "python3 <(curl -fsSL gh.dayl.in/viv/viv.py)"
)
def _get_sources(self) -> None:
self.local_source: Path | str
self.running_source = Path(__file__).resolve()
self.local = not str(self.running_source).startswith("/proc/")
if self.local:
self.local_source = self.running_source
self.local_version = __version__
else:
try:
_local_viv = __import__("viv")
self.local_source = (
_local_viv.__file__ if _local_viv.__file__ else "Not Found"
)
self.local_version = _local_viv.__version__
except ImportError:
self.local_source = self.local_version = "Not Found"
self.git = self.local_source != "Not Found" and str(self.local_source).endswith(
"src/viv/__init__.py"
)
def _match_vivenv(self, name_id: str) -> ViVenv: # type: ignore[return]
# TODO: improve matching algorithm to favor names over id's
@ -895,11 +1007,125 @@ class Viv:
vivenv.dump_info()
def _install_local_src(self, sha256: str, src: Path, cli: Path) -> None:
echo("updating local source copy of viv")
shutil.copy(c.srccache / f"{sha256}.py", src)
make_executable(src)
echo("symlinking cli")
if not cli.is_file():
cli.symlink_to(src)
else:
cli.unlink()
cli.symlink_to(src)
echo("Remember to include the following line in your shell rc file:")
sys.stderr.write(
' export PYTHONPATH="$PYTHONPATH:$HOME/'
f'{src.relative_to(Path.home())}"\n'
)
def manage(self, args: Namespace) -> None:
"""manage viv itself"""
if args.cmd == "show":
if args.pythonpath:
if not self.local:
error("expected to find a local installation", exit=1)
else:
sys.stdout.write(str(self.local_source.parent) + "\n")
else:
echo("Current:")
sys.stderr.write(
SHOW_TEMPLATE.format(
version=__version__,
cli=shutil.which("viv"),
running_src=self.running_source,
local_src=self.local_source,
)
)
elif args.cmd == "update":
if self.local_source == "Not Found":
error(
a.style("viv manage update", "bold")
+ " should be used with an exisiting installation",
1,
)
if self.git:
error(
a.style("viv manage update", "bold")
+ " shouldn't be used with a git-based installation",
1,
)
sha256 = fetch_source(args.reference)
sys.path.append(str(c.srccache))
next_version = __import__(sha256).__version__
if self.local_version == next_version:
echo(f"no change between {args.reference} and local version")
sys.exit(0)
if confirm(
"Would you like to perform the above installation steps?",
UPDATE_TEMPLATE.format(
src_location=self.local_source,
local_version=self.local_version,
cli_location=args.cli,
next_version=next_version,
),
):
self._install_local_src(
sha256,
Path(
args.src
if self.local_source == "Not Found"
else self.local_source,
),
args.cli,
)
elif args.cmd == "install":
if not self.local_source == "Not Found":
error(f"found existing viv installation at {self.local_source}")
echo(
"use "
+ a.style("viv manage update", "bold")
+ " to modify current installation.",
style="red",
)
sys.exit(1)
sha256 = fetch_source(args.reference)
sys.path.append(str(c.srccache))
downloaded_version = __import__(sha256).__version__
echo(f"Downloaded version: {downloaded_version}")
# TODO: see if file is actually where
# we are about to install and give more instructions
if confirm(
"Would you like to perform the above installation steps?",
INSTALL_TEMPLATE.format(
src_location=args.src,
cli_location=args.cli,
),
):
self._install_local_src(sha256, args.src, args.cli)
def shim(self, args):
"""generate viv-powered cli apps"""
echo("not implemented.")
def _get_subcmd_parser(
self, subparsers: _SubParsersAction[ArgumentParser], name: str, **kwargs: Any
self,
subparsers: _SubParsersAction[ArgumentParser],
name: str,
attr: Optional[str] = None,
**kwargs: Any,
) -> ArgumentParser:
aliases = kwargs.pop("aliases", [name[0]])
cmd = getattr(self, name)
cmd = getattr(self, attr if attr else name)
parser: ArgumentParser = subparsers.add_parser(
name,
help=cmd.__doc__.splitlines()[0],
@ -914,7 +1140,7 @@ class Viv:
def cli(self) -> None:
"""cli entrypoint"""
parser = ArgumentParser(description=description)
parser = ArgumentParser(prog=self.name, description=description)
parser.add_argument(
"-V",
"--version",
@ -927,10 +1153,7 @@ class Viv:
)
p_vivenv_arg = ArgumentParser(add_help=False)
p_vivenv_arg.add_argument("vivenv", help="name/hash of vivenv")
p_list = self._get_subcmd_parser(
subparsers,
"list",
)
p_list = self._get_subcmd_parser(subparsers, "list")
p_list.add_argument(
"-q",
@ -955,16 +1178,15 @@ class Viv:
nargs="*",
)
p_exe_python = p_exe_sub.add_parser(
p_exe_sub.add_parser(
"python",
help="run command with python",
parents=[p_vivenv_arg, p_exe_shared],
)
p_exe_pip = p_exe_sub.add_parser(
).set_defaults(func=self.exe, exe="python")
p_exe_sub.add_parser(
"pip", help="run command with pip", parents=[p_vivenv_arg, p_exe_shared]
)
p_exe_python.set_defaults(func=self.exe, exe="python")
p_exe_pip.set_defaults(func=self.exe, exe="pip")
).set_defaults(func=self.exe, exe="pip")
p_remove = self._get_subcmd_parser(
subparsers,
@ -1008,6 +1230,57 @@ class Viv:
"info",
parents=[p_vivenv_arg],
)
p_manage_shared = ArgumentParser(add_help=False)
p_manage_shared.add_argument(
"-r",
"--reference",
help="git reference (branch/tag/commit)",
default="main",
)
p_manage_shared.add_argument(
"-s",
"--src",
help="path/to/source_file",
default=c.srcdefault,
)
p_manage_shared.add_argument(
"-c",
"--cli",
help="path/to/cli (symlink to src)",
default=Path.home() / "bin" / "viv",
)
p_manage_sub = self._get_subcmd_parser(
subparsers,
name="manage",
).add_subparsers(title="subcommand", metavar="<sub-cmd>", required=True)
p_manage_sub.add_parser(
"install", help="install viv", aliases="i", parents=[p_manage_shared]
).set_defaults(func=self.manage, cmd="install")
p_manage_sub.add_parser(
"update",
help="update viv version",
aliases="u",
parents=[p_manage_shared],
).set_defaults(func=self.manage, cmd="update")
(
p_manage_show := p_manage_sub.add_parser(
"show", help="show current installation info", aliases="s"
)
).set_defaults(func=self.manage, cmd="show")
p_manage_show.add_argument(
"-p", "--pythonpath", help="show the path/to/install", action="store_true"
)
self._get_subcmd_parser(
subparsers,
"shim",
).set_defaults(func =self.shim, cmd="shim")
args = parser.parse_args()