Compare commits

..

No commits in common. "c5d9d8cb6521bd936999f6bdbfa6ddc26ef0ab41" and "ed8a4458c9819f09a172629c17a90b9cd9d66c7c" have entirely different histories.

14 changed files with 175 additions and 660 deletions

View file

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

View file

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

View file

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

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 KiB

11
docs/demo.py Normal file
View file

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

23
docs/demo.tape Normal file
View file

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

23
docs/freeze.tape Normal file
View file

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

View file

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

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

18
docs/usage.md Normal file
View file

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

@ -9,6 +9,8 @@ viv shim black -o ~/bin/black
""" """
import sys import sys
sys.path.append("/home/daylin/.viv/src") # noqa
import subprocess import subprocess
import viv import viv

View file

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

View file

@ -21,8 +21,6 @@ import sys
import tempfile import tempfile
import threading import threading
import time import time
from urllib.request import urlopen
from urllib.error import HTTPError
import venv import venv
from argparse import SUPPRESS, Action from argparse import SUPPRESS, Action
from argparse import ArgumentParser as StdArgParser from argparse import ArgumentParser as StdArgParser
@ -41,7 +39,6 @@ from types import TracebackType
from typing import ( from typing import (
Any, Any,
Dict, Dict,
Generator,
List, List,
NoReturn, NoReturn,
Optional, Optional,
@ -49,9 +46,10 @@ from typing import (
TextIO, TextIO,
Tuple, Tuple,
Type, Type,
Generator,
) )
__version__ = "23.5a1" __version__ = "22.12a3-41-g5c40210-dev"
@dataclass @dataclass
@ -61,22 +59,9 @@ class Config:
venvcache: Path = ( venvcache: Path = (
Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache")) / "viv" / "venvs" Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache")) / "viv" / "venvs"
) )
srccache: Path = (
Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache")) / "viv" / "src"
)
srcdefault: Path = (
Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local" / "share"))
/ "viv"
/ "viv.py"
)
def __post_init__(self) -> None: def __post_init__(self) -> None:
self.venvcache.mkdir(parents=True, exist_ok=True) self.venvcache.mkdir(parents=True, exist_ok=True)
self.srccache.mkdir(
parents=True,
exist_ok=True,
)
self.srcdefault.parent.mkdir(parents=True, exist_ok=True)
c = Config() c = Config()
@ -250,9 +235,6 @@ class Ansi:
else: else:
return (row,) 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( def table(
self, rows: Tuple[Tuple[str, Sequence[str]], ...], header_style: str = "cyan" self, rows: Tuple[Tuple[str, Sequence[str]], ...], header_style: str = "cyan"
) -> None: ) -> None:
@ -317,27 +299,12 @@ def echo(
msg: str, style: str = "magenta", newline: bool = True, fd: TextIO = sys.stderr msg: str, style: str = "magenta", newline: bool = True, fd: TextIO = sys.stderr
) -> None: ) -> None:
"""output general message to stdout""" """output general message to stdout"""
output = f"{a.viv_preamble(style)} {msg}" output = f"{a.cyan}Viv{a.end}{a.__dict__[style]}::{a.end} {msg}"
if newline: if newline:
output += "\n" output += "\n"
fd.write(output) 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( def run(
command: List[str], command: List[str],
spinmsg: str = "", spinmsg: str = "",
@ -481,7 +448,7 @@ class ViVenv:
a.table((("key", "value"), *((k, v) for k, v in info.items()))) a.table((("key", "value"), *((k, v) for k, v in info.items())))
def use(*packages: str, track_exe: bool = False, name: str = "") -> ViVenv: def use(*packages: str, track_exe: bool = False, name: str = "") -> None:
"""create a vivenv and append to sys.path """create a vivenv and append to sys.path
Args: Args:
@ -500,7 +467,6 @@ def use(*packages: str, track_exe: bool = False, name: str = "") -> ViVenv:
vivenv.dump_info(write=True) vivenv.dump_info(write=True)
modify_sys_path(vivenv.path) modify_sys_path(vivenv.path)
return vivenv
def validate_spec(spec: Tuple[str, ...]) -> None: def validate_spec(spec: Tuple[str, ...]) -> None:
@ -549,7 +515,8 @@ STANDALONE_TEMPLATE = r"""
# >>>>> code golfed with <3 # >>>>> code golfed with <3
""" # noqa """ # 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] 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") 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 ge,sys,P,ew=i("os").getenv,i("sys"),i("pathlib").Path,i("sys").stderr.write
@ -568,27 +535,9 @@ STANDALONE_TEMPLATE_USE = r"""def _viv_use(*pkgs: str, track_exe: bool = False,
i("json").dump({{"created":s(i("datetime").datetime.today()),"id":_id,"spec":spec,"exe":exe}},f) 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] sys.path = [p for p in (*sys.path,s(*(env/"lib").glob("py*/si*"))) if p!=i("site").USER_SITE]
_viv_use({spec}) _viv_use({spec})
""" # noqa """[ # noqa
1:
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: def noqa(txt: str) -> str:
@ -742,7 +691,7 @@ class CustomHelpFormatter(RawDescriptionHelpFormatter, HelpFormatter):
# determine the required width and the entry label # determine the required width and the entry label
help_position = min(self._action_max_length + 2, self._max_help_position) help_position = min(self._action_max_length + 2, self._max_help_position)
help_width = max(self._width - help_position, 11) help_width = max(self._width - help_position, 11)
action_width = help_position - self._current_indent action_width = help_position - self._current_indent - 2
action_header = self._format_action_invocation(action) action_header = self._format_action_invocation(action)
action_header_len = len(a.escape(action_header)) action_header_len = len(a.escape(action_header))
@ -814,8 +763,7 @@ class ArgumentParser(StdArgParser):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.formatter_class = lambda prog: CustomHelpFormatter( self.formatter_class = lambda prog: CustomHelpFormatter(
prog, prog, max_help_position=35
max_help_position=35,
) )
def error(self, message: str) -> NoReturn: def error(self, message: str) -> NoReturn:
@ -828,76 +776,18 @@ class ArgumentParser(StdArgParser):
description = f""" description = f"""
{a.tagline()} {a.tagline()}
to create/activate a vivenv:
- from command line: `{a.style("viv -h","bold")}` {a.style('create/activate a vivenv','underline')}
- within python script: {a.style('__import__("viv").use("typer", "rich-click")','bold')} 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: class Viv:
def __init__(self) -> None: def __init__(self) -> None:
self.vivenvs = get_venvs() 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] def _match_vivenv(self, name_id: str) -> ViVenv: # type: ignore[return]
# TODO: improve matching algorithm to favor names over id's # TODO: improve matching algorithm to favor names over id's
@ -1005,116 +895,11 @@ class Viv:
vivenv.dump_info() 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":
echo("Current:")
sys.stdout.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 _get_subcmd_parser( def _get_subcmd_parser(
self, self, subparsers: _SubParsersAction[ArgumentParser], name: str, **kwargs: Any
subparsers: _SubParsersAction[ArgumentParser],
name: str,
attr: Optional[str] = None,
**kwargs: Any,
) -> ArgumentParser: ) -> ArgumentParser:
aliases = kwargs.pop("aliases", [name[0]]) aliases = kwargs.pop("aliases", [name[0]])
cmd = getattr(self, attr if attr else name) cmd = getattr(self, name)
parser: ArgumentParser = subparsers.add_parser( parser: ArgumentParser = subparsers.add_parser(
name, name,
help=cmd.__doc__.splitlines()[0], help=cmd.__doc__.splitlines()[0],
@ -1129,7 +914,7 @@ class Viv:
def cli(self) -> None: def cli(self) -> None:
"""cli entrypoint""" """cli entrypoint"""
parser = ArgumentParser(prog=self.name, description=description) parser = ArgumentParser(description=description)
parser.add_argument( parser.add_argument(
"-V", "-V",
"--version", "--version",
@ -1142,7 +927,10 @@ class Viv:
) )
p_vivenv_arg = ArgumentParser(add_help=False) p_vivenv_arg = ArgumentParser(add_help=False)
p_vivenv_arg.add_argument("vivenv", help="name/hash of vivenv") 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( p_list.add_argument(
"-q", "-q",
@ -1167,15 +955,16 @@ class Viv:
nargs="*", nargs="*",
) )
p_exe_sub.add_parser( p_exe_python = p_exe_sub.add_parser(
"python", "python",
help="run command with python", help="run command with python",
parents=[p_vivenv_arg, p_exe_shared], parents=[p_vivenv_arg, p_exe_shared],
).set_defaults(func=self.exe, exe="python") )
p_exe_pip = p_exe_sub.add_parser(
p_exe_sub.add_parser(
"pip", help="run command with pip", parents=[p_vivenv_arg, p_exe_shared] "pip", help="run command with pip", parents=[p_vivenv_arg, p_exe_shared]
).set_defaults(func=self.exe, exe="pip") )
p_exe_python.set_defaults(func=self.exe, exe="python")
p_exe_pip.set_defaults(func=self.exe, exe="pip")
p_remove = self._get_subcmd_parser( p_remove = self._get_subcmd_parser(
subparsers, subparsers,
@ -1219,46 +1008,6 @@ class Viv:
"info", "info",
parents=[p_vivenv_arg], 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_sub.add_parser(
"show", help="show current installation info", aliases="s"
).set_defaults(func=self.manage, cmd="show")
args = parser.parse_args() args = parser.parse_args()