envon 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- envon-0.0.1/LICENSE +21 -0
- envon-0.0.1/PKG-INFO +113 -0
- envon-0.0.1/README.md +72 -0
- envon-0.0.1/pyproject.toml +54 -0
- envon-0.0.1/src/envon/__init__.py +3 -0
- envon-0.0.1/src/envon/__main__.py +4 -0
- envon-0.0.1/src/envon/bootstrap_bash.sh +12 -0
- envon-0.0.1/src/envon/bootstrap_csh.csh +102 -0
- envon-0.0.1/src/envon/bootstrap_csh_fixed.csh +2 -0
- envon-0.0.1/src/envon/bootstrap_fish.fish +17 -0
- envon-0.0.1/src/envon/bootstrap_nushell.nu +19 -0
- envon-0.0.1/src/envon/bootstrap_powershell.ps1 +14 -0
- envon-0.0.1/src/envon/bootstrap_sh.sh +11 -0
- envon-0.0.1/src/envon/envon.py +843 -0
envon-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 USER1995
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
envon-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envon
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Emit the activation command for the nearest Python virtual environment and install shell bootstrap wrappers.
|
|
5
|
+
Project-URL: Homepage, https://github.com/userfrom1995/envon
|
|
6
|
+
Project-URL: Repository, https://github.com/userfrom1995/envon
|
|
7
|
+
Author-email: User1995 <userfrom1995@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 USER1995
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: activation,bootstrap,shell,venv,virtualenv
|
|
31
|
+
Classifier: Environment :: Console
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
36
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
37
|
+
Classifier: Topic :: System :: Shells
|
|
38
|
+
Requires-Python: >=3.8
|
|
39
|
+
Requires-Dist: virtualenv>=20
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# envon
|
|
43
|
+
|
|
44
|
+
Emit the activation command for the nearest or specified Python virtual environment, and install shell bootstrap wrappers for seamless activation in your favorite shell.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
- Auto-detects and activates Python virtual environments in your project.
|
|
48
|
+
- Supports multiple shells: bash, zsh, sh, fish, powershell, pwsh, nushell, cmd, csh/tcsh/cshell.
|
|
49
|
+
- Installs a shell bootstrap function for one-command activation.
|
|
50
|
+
- Flexible CLI flags for advanced usage.
|
|
51
|
+
|
|
52
|
+
## Supported Shells
|
|
53
|
+
- **bash** (full auto-activation)
|
|
54
|
+
- **zsh** (full auto-activation)
|
|
55
|
+
- **sh** (full auto-activation)
|
|
56
|
+
- **fish** (full auto-activation)
|
|
57
|
+
- **powershell**, **pwsh** (full auto-activation)
|
|
58
|
+
- **cmd**, **batch**, **bat** (prints command for manual activation)
|
|
59
|
+
- **nushell**, **nu** (prints command for manual activation)
|
|
60
|
+
- **csh**, **tcsh**, **cshell** (prints command for manual activation)
|
|
61
|
+
|
|
62
|
+
For detailed shell support and limitations, see [docs/user_guide.md](https://github.com/userfrom1995/envon/blob/main/docs/user_guide.md).
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
**Recommended:** Install with pipx for isolated environments:
|
|
66
|
+
```bash
|
|
67
|
+
pipx install envon
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Alternative:** Install with pip (may fail on some distros like Ubuntu or Windows due to PEP 668):
|
|
71
|
+
```bash
|
|
72
|
+
python3 -m pip install envon
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
After installation, run:
|
|
76
|
+
```bash
|
|
77
|
+
envon --install
|
|
78
|
+
```
|
|
79
|
+
This detects your shell and sets up the bootstrap for auto-activation.
|
|
80
|
+
|
|
81
|
+
For more detailed installation instructions, see [docs/installation.md](https://github.com/userfrom1995/envon/blob/main/docs/installation.md).
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
After installation and bootstrap setup, run:
|
|
85
|
+
```bash
|
|
86
|
+
envon
|
|
87
|
+
```
|
|
88
|
+
This will activate the nearest virtual environment in your project.
|
|
89
|
+
|
|
90
|
+
Supported flags: `--emit [SHELL]`, `--print-path`, `--install [SHELL]`.
|
|
91
|
+
|
|
92
|
+
For advanced usage, examples, and all flags, see [docs/user_guide.md](https://github.com/userfrom1995/envon/blob/main/docs/user_guide.md).
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
For development setup, building, and project structure, see [docs/development.md](https://github.com/userfrom1995/envon/blob/main/docs/development.md).
|
|
96
|
+
|
|
97
|
+
## Contributor Note
|
|
98
|
+
|
|
99
|
+
**envon is in its early phase. Basic functionality is solid, but we welcome help!**
|
|
100
|
+
- TCSH/cshell and Nushell support need improvement (auto-activation, overlays).
|
|
101
|
+
- If you find issues, please [raise an issue](https://github.com/userfrom1995/envon/issues).
|
|
102
|
+
- If you'd like to contribute, fork and submit a PR—contributions are very welcome!
|
|
103
|
+
|
|
104
|
+
Let's make envon the best Python venv activator for every shell!
|
|
105
|
+
|
|
106
|
+
## Release Notes
|
|
107
|
+
|
|
108
|
+
**Version 0.1.0 (First Release)**
|
|
109
|
+
This is the initial release of envon. A lot of work is still ongoing, especially in the testing, CI, and adding support for missing shells (e.g., full auto-activation for Nushell and csh/tcsh).
|
|
110
|
+
|
|
111
|
+
If you see any issues, feel free to [open an issue](https://github.com/userfrom1995/envon/issues). If you're interested in contributing, feel free to submit a PR. If you have ideas or anything regarding the project, feel free to open a discussion or feature request in an issue.
|
|
112
|
+
|
|
113
|
+
Check out the project on [PyPI](https://pypi.org/project/envon/).
|
envon-0.0.1/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# envon
|
|
2
|
+
|
|
3
|
+
Emit the activation command for the nearest or specified Python virtual environment, and install shell bootstrap wrappers for seamless activation in your favorite shell.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Auto-detects and activates Python virtual environments in your project.
|
|
7
|
+
- Supports multiple shells: bash, zsh, sh, fish, powershell, pwsh, nushell, cmd, csh/tcsh/cshell.
|
|
8
|
+
- Installs a shell bootstrap function for one-command activation.
|
|
9
|
+
- Flexible CLI flags for advanced usage.
|
|
10
|
+
|
|
11
|
+
## Supported Shells
|
|
12
|
+
- **bash** (full auto-activation)
|
|
13
|
+
- **zsh** (full auto-activation)
|
|
14
|
+
- **sh** (full auto-activation)
|
|
15
|
+
- **fish** (full auto-activation)
|
|
16
|
+
- **powershell**, **pwsh** (full auto-activation)
|
|
17
|
+
- **cmd**, **batch**, **bat** (prints command for manual activation)
|
|
18
|
+
- **nushell**, **nu** (prints command for manual activation)
|
|
19
|
+
- **csh**, **tcsh**, **cshell** (prints command for manual activation)
|
|
20
|
+
|
|
21
|
+
For detailed shell support and limitations, see [docs/user_guide.md](https://github.com/userfrom1995/envon/blob/main/docs/user_guide.md).
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
**Recommended:** Install with pipx for isolated environments:
|
|
25
|
+
```bash
|
|
26
|
+
pipx install envon
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Alternative:** Install with pip (may fail on some distros like Ubuntu or Windows due to PEP 668):
|
|
30
|
+
```bash
|
|
31
|
+
python3 -m pip install envon
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
After installation, run:
|
|
35
|
+
```bash
|
|
36
|
+
envon --install
|
|
37
|
+
```
|
|
38
|
+
This detects your shell and sets up the bootstrap for auto-activation.
|
|
39
|
+
|
|
40
|
+
For more detailed installation instructions, see [docs/installation.md](https://github.com/userfrom1995/envon/blob/main/docs/installation.md).
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
After installation and bootstrap setup, run:
|
|
44
|
+
```bash
|
|
45
|
+
envon
|
|
46
|
+
```
|
|
47
|
+
This will activate the nearest virtual environment in your project.
|
|
48
|
+
|
|
49
|
+
Supported flags: `--emit [SHELL]`, `--print-path`, `--install [SHELL]`.
|
|
50
|
+
|
|
51
|
+
For advanced usage, examples, and all flags, see [docs/user_guide.md](https://github.com/userfrom1995/envon/blob/main/docs/user_guide.md).
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
For development setup, building, and project structure, see [docs/development.md](https://github.com/userfrom1995/envon/blob/main/docs/development.md).
|
|
55
|
+
|
|
56
|
+
## Contributor Note
|
|
57
|
+
|
|
58
|
+
**envon is in its early phase. Basic functionality is solid, but we welcome help!**
|
|
59
|
+
- TCSH/cshell and Nushell support need improvement (auto-activation, overlays).
|
|
60
|
+
- If you find issues, please [raise an issue](https://github.com/userfrom1995/envon/issues).
|
|
61
|
+
- If you'd like to contribute, fork and submit a PR—contributions are very welcome!
|
|
62
|
+
|
|
63
|
+
Let's make envon the best Python venv activator for every shell!
|
|
64
|
+
|
|
65
|
+
## Release Notes
|
|
66
|
+
|
|
67
|
+
**Version 0.1.0 (First Release)**
|
|
68
|
+
This is the initial release of envon. A lot of work is still ongoing, especially in the testing, CI, and adding support for missing shells (e.g., full auto-activation for Nushell and csh/tcsh).
|
|
69
|
+
|
|
70
|
+
If you see any issues, feel free to [open an issue](https://github.com/userfrom1995/envon/issues). If you're interested in contributing, feel free to submit a PR. If you have ideas or anything regarding the project, feel free to open a discussion or feature request in an issue.
|
|
71
|
+
|
|
72
|
+
Check out the project on [PyPI](https://pypi.org/project/envon/).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "envon"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Emit the activation command for the nearest Python virtual environment and install shell bootstrap wrappers."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
keywords = ["virtualenv", "venv", "activation", "shell", "bootstrap"]
|
|
13
|
+
authors = [{ name = "User1995", email = "userfrom1995@gmail.com" }]
|
|
14
|
+
urls = { "Homepage" = "https://github.com/userfrom1995/envon", "Repository" = "https://github.com/userfrom1995/envon" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Topic :: System :: Shells",
|
|
22
|
+
"Topic :: Software Development :: Build Tools",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Require virtualenv so activator plugins are always available
|
|
26
|
+
dependencies = [
|
|
27
|
+
"virtualenv>=20"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
envon = "envon.envon:main"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/envon"]
|
|
35
|
+
|
|
36
|
+
# Ensure non-Python bootstrap files are included
|
|
37
|
+
[tool.hatch.build]
|
|
38
|
+
include = [
|
|
39
|
+
"src/envon/bootstrap_*.sh",
|
|
40
|
+
"src/envon/bootstrap_*.fish",
|
|
41
|
+
"src/envon/bootstrap_*.ps1",
|
|
42
|
+
"src/envon/bootstrap_csh.csh",
|
|
43
|
+
"src/envon/bootstrap_*.nu",
|
|
44
|
+
"src/envon/*.py",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.sdist]
|
|
50
|
+
include = [
|
|
51
|
+
"src/envon/**",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE",
|
|
54
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
envon() {
|
|
2
|
+
if [ "$#" -gt 0 ]; then
|
|
3
|
+
case "$1" in
|
|
4
|
+
help|-h|--help|--install) command envon "$@"; return $? ;;
|
|
5
|
+
-*) command envon "$@"; return $? ;;
|
|
6
|
+
esac
|
|
7
|
+
fi
|
|
8
|
+
local cmd ec
|
|
9
|
+
cmd="$(command envon "$@")"; ec=$?
|
|
10
|
+
if [ $ec -ne 0 ]; then printf %s\n "$cmd" >&2; return $ec; fi
|
|
11
|
+
eval "$cmd"
|
|
12
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!usr/bin/tcsh -f
|
|
2
|
+
#! /bin/tcsh -f
|
|
3
|
+
# envon wrapper script for tcsh compatibility
|
|
4
|
+
|
|
5
|
+
# Check if this is a help/install command
|
|
6
|
+
# alias envon 'if ( $#argv >= 1 ) then
|
|
7
|
+
# if ( "$argv[1]" == "help" || "$argv[1]" == "-h" || "$argv[1]" == "--help" || "$argv[1]" == "--install" ) then
|
|
8
|
+
# exec /usr/bin/envon $argv:q
|
|
9
|
+
# endif
|
|
10
|
+
# endif
|
|
11
|
+
|
|
12
|
+
# # For environment activation
|
|
13
|
+
# set _ev=`~/.local/bin/envon $argv:q`
|
|
14
|
+
# if ( $status == 0 && "$_ev" != "" ) then
|
|
15
|
+
# eval "$_ev"
|
|
16
|
+
# endif'
|
|
17
|
+
|
|
18
|
+
# alias envon `if ( $#argv >= 1 ) then
|
|
19
|
+
# if ( "$argv[1]" == "help" || "$argv[1]" == "-h" || "$argv[1]" == "--help" || "$argv[1]" == "--install" ) then
|
|
20
|
+
# exec /usr/bin/envon $argv:q
|
|
21
|
+
# endif
|
|
22
|
+
# endif
|
|
23
|
+
# set _ev=`~/.local/bin/envon $argv:q`
|
|
24
|
+
# if ( $status == 0 && "$_ev" != "" ) then
|
|
25
|
+
# eval "$_ev"
|
|
26
|
+
# endif`
|
|
27
|
+
|
|
28
|
+
# alias envon 'if ( $#argv >= 1 ) then \
|
|
29
|
+
# if ( "$argv[1]" == "help" || "$argv[1]" == "-h" || "$argv[1]" == "--help" || "$argv[1]" == "--install" ) then \
|
|
30
|
+
# ~/.local/bin/envon \!* \
|
|
31
|
+
# else \
|
|
32
|
+
# set _ev=`~/.local/bin/envon \!*` \
|
|
33
|
+
# if ( $status == 0 && "$_ev" != "" ) then \
|
|
34
|
+
# eval "$_ev" \
|
|
35
|
+
# endif \
|
|
36
|
+
# if ( $?_ev ) unset _ev \
|
|
37
|
+
# endif \
|
|
38
|
+
# else \
|
|
39
|
+
# set _ev=`~/.local/bin/envon` \
|
|
40
|
+
# if ( $status == 0 && "$_ev" != "" ) then \
|
|
41
|
+
# eval "$_ev" \
|
|
42
|
+
# endif \
|
|
43
|
+
# if ( $?_ev ) unset _ev \
|
|
44
|
+
# endif'
|
|
45
|
+
# For tcsh, we need to avoid complex control structures in aliases
|
|
46
|
+
# Instead, we'll use a very simple approach
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# alias envon 'set _cmd="\!*"; if ( "$_cmd" == "--help" || "$_cmd" == "-h" || "$_cmd" == "help" || "$_cmd" == "--install" ) ~/.local/bin/envon \!*; if ( "$_cmd" != "--help" && "$_cmd" != "-h" && "$_cmd" != "help" && "$_cmd" != "--install" ) set _result="`~/.local/bin/envon \!*`" && if ( $status == 0 ) eval "$_result"; unset _cmd; if ( $?_result ) unset _result'
|
|
50
|
+
|
|
51
|
+
# alias envon 'if ( $#argv >= 1 ) then \
|
|
52
|
+
# if ( "$argv[1]" == "help" || "$argv[1]" == "-h" || "$argv[1]" == "--help" || "$argv[1]" == "--install" ) then \
|
|
53
|
+
# exec /.local/bin/envon $argv:q \
|
|
54
|
+
# endif \
|
|
55
|
+
# endif \
|
|
56
|
+
# set _ev=`~/.local/bin/envon $argv:q` \
|
|
57
|
+
# if ( $status == 0 && "$_ev" != "" ) then \
|
|
58
|
+
# eval "$_ev" \
|
|
59
|
+
# endif'
|
|
60
|
+
|
|
61
|
+
# alias envon `if ( $#argv >= 1 ) then
|
|
62
|
+
# if ( "$argv[1]" == "help" || "$argv[1]" == "-h" || "$argv[1]" == "--help" || "$argv[1]" == "--install" ) then
|
|
63
|
+
# exec /usr/bin/envon $argv:q
|
|
64
|
+
# endif
|
|
65
|
+
# endif
|
|
66
|
+
# set _ev=`~/.local/bin/envon $argv:q`
|
|
67
|
+
# if ( $status == 0 && "$_ev" != "" ) then
|
|
68
|
+
# eval "$_ev"
|
|
69
|
+
# endif`
|
|
70
|
+
|
|
71
|
+
# envon managed bootstrap - minimal fixes applied
|
|
72
|
+
# Define a shell function for envon
|
|
73
|
+
# alias envon 'envon_func \!*'
|
|
74
|
+
|
|
75
|
+
# envon_func:
|
|
76
|
+
# if ( $#argv >= 1 ) then
|
|
77
|
+
# if ("$argv[1]" == "help" || "$argv[1]" == "-h" || "$argv[1]" == "--help" || "$argv[1]" == "--install") then
|
|
78
|
+
# exec ~/.local/bin/envon $argv:q
|
|
79
|
+
# endif
|
|
80
|
+
# endif
|
|
81
|
+
# set _ev=`~/.local/bin/envon $argv:q`
|
|
82
|
+
# if ( $status == 0 && "$_ev" != "" ) then
|
|
83
|
+
# eval "$_ev"
|
|
84
|
+
# endif
|
|
85
|
+
# return
|
|
86
|
+
|
|
87
|
+
#!/bin/tcsh -f
|
|
88
|
+
# envon wrapper script for tcsh
|
|
89
|
+
|
|
90
|
+
# Check if this is a help/install command
|
|
91
|
+
# if ( $#argv >= 1 ) then
|
|
92
|
+
# if ( "$argv[1]" == "help" || "$argv[1]" == "-h" || "$argv[1]" == "--help" || "$argv[1]" == "--install" ) then
|
|
93
|
+
# exec ~/.local/bin/envon $argv:q
|
|
94
|
+
# endif
|
|
95
|
+
# endif
|
|
96
|
+
|
|
97
|
+
# # For environment activation
|
|
98
|
+
# set _ev=`~/.local/bin/envon $argv:q`
|
|
99
|
+
# if ( $status == 0 && "$_ev" != "" ) then
|
|
100
|
+
# eval "$_ev"
|
|
101
|
+
# endif
|
|
102
|
+
alias envon '~/.local/bin/envon \!*'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function envon
|
|
2
|
+
if test (count $argv) -gt 0
|
|
3
|
+
set first $argv[1]
|
|
4
|
+
if test "$first" = "--"
|
|
5
|
+
set -e argv[1]
|
|
6
|
+
else if string match -rq '^(help|-h|--help|--install|-).*' -- $first
|
|
7
|
+
command envon $argv
|
|
8
|
+
return $status
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
set cmd (command envon $argv)
|
|
12
|
+
if test $status -ne 0
|
|
13
|
+
echo $cmd >&2
|
|
14
|
+
return 1
|
|
15
|
+
end
|
|
16
|
+
eval $cmd
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
def --env envon [...args] {
|
|
2
|
+
if ($args | is-empty) == false {
|
|
3
|
+
let first = ($args | first)
|
|
4
|
+
if $first == '--' { let args = ($args | skip 1); ^envon ...$args; return }
|
|
5
|
+
if ($first == 'help') or ($first == '-h') or ($first == '--help') or ($first == '--install') or (($first | str starts-with '-') == true) {
|
|
6
|
+
^envon ...$args; return
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
let venv = (^envon --print-path ...$args | str trim)
|
|
10
|
+
if ($venv | is-empty) { return }
|
|
11
|
+
let act = ($venv | path join 'bin' 'activate.nu')
|
|
12
|
+
if ($act | path exists) {
|
|
13
|
+
echo $"overlay use '($act | path expand)'"
|
|
14
|
+
echo 'Run the printed command in your interactive shell to activate the virtual environment.'
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
echo 'Nushell activation script (activate.nu) not found for this virtual environment.'
|
|
18
|
+
echo 'Create or upgrade the environment with a tool that generates Nushell activation scripts.'
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function envon {
|
|
2
|
+
param([Parameter(ValueFromRemainingArguments=$true)][string[]]$Args)
|
|
3
|
+
$envonExe = Get-Command envon -CommandType Application -ErrorAction SilentlyContinue
|
|
4
|
+
if (-not $envonExe) { Write-Error 'envon console script not found on PATH'; return }
|
|
5
|
+
if ($Args.Count -gt 0) {
|
|
6
|
+
if ($Args[0] -eq '--') { $Args = $Args[1..($Args.Count-1)] }
|
|
7
|
+
elseif ($Args[0] -eq 'help' -or $Args[0] -eq '--help' -or $Args[0] -eq '--install' -or $Args[0].StartsWith('-')) {
|
|
8
|
+
& $envonExe.Source @Args; return
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
$cmd = & $envonExe.Source @Args
|
|
12
|
+
if ($LASTEXITCODE -ne 0) { Write-Error $cmd; return }
|
|
13
|
+
Invoke-Expression $cmd
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
envon() {
|
|
2
|
+
if [ "$#" -gt 0 ]; then
|
|
3
|
+
case "$1" in
|
|
4
|
+
help|-h|--help|--install) command envon "$@"; return $? ;;
|
|
5
|
+
-*) command envon "$@"; return $? ;;
|
|
6
|
+
esac
|
|
7
|
+
fi
|
|
8
|
+
cmd=$(command envon "$@"); ec=$?
|
|
9
|
+
if [ $ec -ne 0 ]; then printf %s\n "$cmd" >&2; return $ec; fi
|
|
10
|
+
eval "$cmd"
|
|
11
|
+
}
|
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import stat
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from virtualenv.run.plugin.base import PluginLoader
|
|
13
|
+
except ImportError:
|
|
14
|
+
# Fallback if plugin system not available
|
|
15
|
+
PluginLoader = None
|
|
16
|
+
|
|
17
|
+
try: # version info for managed bootstrap tagging
|
|
18
|
+
from virtualenv.version import __version__ as VENV_VERSION
|
|
19
|
+
except Exception: # pragma: no cover - defensive fallback
|
|
20
|
+
VENV_VERSION = "unknown"
|
|
21
|
+
|
|
22
|
+
PREFERRED_NAMES = (".venv", "venv", "env", ".env")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class EnvonError(Exception):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_venv_dir(path: Path) -> bool:
|
|
30
|
+
"""Return True if the given path looks like a Python virtual environment directory."""
|
|
31
|
+
if not path or not path.is_dir():
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
# Check for pyvenv.cfg file - this is the most reliable indicator
|
|
35
|
+
if (path / "pyvenv.cfg").exists():
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
# Try to use virtualenv's activation system to detect available scripts
|
|
39
|
+
if PluginLoader:
|
|
40
|
+
try:
|
|
41
|
+
activators = PluginLoader.entry_points_for("virtualenv.activate")
|
|
42
|
+
# Check if any activation scripts exist
|
|
43
|
+
for activator_name in ["bash", "batch", "powershell", "fish", "cshell", "nushell"]:
|
|
44
|
+
if activator_name in activators:
|
|
45
|
+
# Check common script locations based on platform
|
|
46
|
+
if activator_name == "bash" and (path / "bin" / "activate").exists():
|
|
47
|
+
return True
|
|
48
|
+
if activator_name == "batch" and (path / "Scripts" / "activate.bat").exists():
|
|
49
|
+
return True
|
|
50
|
+
if activator_name == "powershell" and (path / "Scripts" / "Activate.ps1").exists():
|
|
51
|
+
return True
|
|
52
|
+
if activator_name == "fish" and (path / "bin" / "activate.fish").exists():
|
|
53
|
+
return True
|
|
54
|
+
if activator_name == "cshell" and (path / "bin" / "activate.csh").exists():
|
|
55
|
+
return True
|
|
56
|
+
if activator_name == "nushell" and (path / "bin" / "activate.nu").exists():
|
|
57
|
+
return True
|
|
58
|
+
except Exception:
|
|
59
|
+
# Fall back to hardcoded detection
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
# Fallback: hardcoded detection for compatibility
|
|
63
|
+
# Windows layout
|
|
64
|
+
if (path / "Scripts" / "activate.bat").exists() or (path / "Scripts" / "Activate.ps1").exists():
|
|
65
|
+
return True
|
|
66
|
+
# POSIX layout
|
|
67
|
+
if (path / "bin" / "activate").exists():
|
|
68
|
+
return True
|
|
69
|
+
# Other shells
|
|
70
|
+
if (path / "bin" / "activate.fish").exists() or (path / "bin" / "activate.csh").exists() or (
|
|
71
|
+
path / "bin" / "activate.nu").exists():
|
|
72
|
+
return True
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def find_nearest_venv(start: Path) -> Path | None:
|
|
77
|
+
"""Walk upwards from start to root and try common names; return the first venv path found."""
|
|
78
|
+
cur = start
|
|
79
|
+
tried: list[Path] = []
|
|
80
|
+
while True:
|
|
81
|
+
for name in PREFERRED_NAMES:
|
|
82
|
+
cand = cur / name
|
|
83
|
+
tried.append(cand)
|
|
84
|
+
if is_venv_dir(cand):
|
|
85
|
+
return cand
|
|
86
|
+
parent = cur.parent
|
|
87
|
+
if parent == cur:
|
|
88
|
+
break
|
|
89
|
+
cur = parent
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _list_venvs_in_dir(root: Path) -> list[Path]:
|
|
94
|
+
"""Return all virtualenv directories directly under root.
|
|
95
|
+
|
|
96
|
+
Preference order: common names first (PREFERRED_NAMES) in that order, then any other subdirectory
|
|
97
|
+
that looks like a venv in alphabetical order.
|
|
98
|
+
"""
|
|
99
|
+
found: list[Path] = []
|
|
100
|
+
seen: set[Path] = set()
|
|
101
|
+
for name in PREFERRED_NAMES:
|
|
102
|
+
cand = root / name
|
|
103
|
+
if is_venv_dir(cand):
|
|
104
|
+
found.append(cand)
|
|
105
|
+
seen.add(cand)
|
|
106
|
+
# Scan all subdirectories
|
|
107
|
+
try:
|
|
108
|
+
for child in sorted([p for p in root.iterdir() if p.is_dir()]):
|
|
109
|
+
if child in seen:
|
|
110
|
+
continue
|
|
111
|
+
if is_venv_dir(child):
|
|
112
|
+
found.append(child)
|
|
113
|
+
except FileNotFoundError:
|
|
114
|
+
pass
|
|
115
|
+
return found
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _choose_interactively(candidates: list[Path], context: str) -> Path:
|
|
119
|
+
"""Prompt the user to choose a venv when multiple are found.
|
|
120
|
+
|
|
121
|
+
If stdin is not a TTY, print options and raise EnvonError.
|
|
122
|
+
"""
|
|
123
|
+
if not sys.stdin.isatty():
|
|
124
|
+
lines = "\n".join(f" {i + 1}) {p}" for i, p in enumerate(candidates))
|
|
125
|
+
raise EnvonError(
|
|
126
|
+
f"Multiple virtual environments found in {context}. Choose one by passing a path or name:\n{lines}"
|
|
127
|
+
)
|
|
128
|
+
print(f"Multiple virtual environments found in {context}:", file=sys.stderr)
|
|
129
|
+
for i, p in enumerate(candidates, 1):
|
|
130
|
+
print(f" {i}) {p}", file=sys.stderr)
|
|
131
|
+
while True:
|
|
132
|
+
# Print prompt to stderr so command substitution doesn't capture it
|
|
133
|
+
sys.stderr.write("Select [1-{}]: ".format(len(candidates)))
|
|
134
|
+
sys.stderr.flush()
|
|
135
|
+
try:
|
|
136
|
+
sel = sys.stdin.readline()
|
|
137
|
+
except Exception:
|
|
138
|
+
raise EnvonError("Aborted.")
|
|
139
|
+
if not sel:
|
|
140
|
+
raise EnvonError("Aborted.")
|
|
141
|
+
sel = sel.strip()
|
|
142
|
+
if not sel:
|
|
143
|
+
continue
|
|
144
|
+
if sel.isdigit():
|
|
145
|
+
idx = int(sel)
|
|
146
|
+
if 1 <= idx <= len(candidates):
|
|
147
|
+
return candidates[idx - 1]
|
|
148
|
+
print("Invalid selection.", file=sys.stderr)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def resolve_target(target: str | None) -> Path:
|
|
152
|
+
if not target:
|
|
153
|
+
# First, prefer venvs directly in the current directory; if multiple, ask.
|
|
154
|
+
cwd = Path.cwd()
|
|
155
|
+
in_here = _list_venvs_in_dir(cwd)
|
|
156
|
+
if len(in_here) == 1:
|
|
157
|
+
return in_here[0]
|
|
158
|
+
if len(in_here) > 1:
|
|
159
|
+
return _choose_interactively(in_here, str(cwd))
|
|
160
|
+
# Fallback to walking upwards to find a named venv (e.g., project/.venv)
|
|
161
|
+
venv = find_nearest_venv(cwd)
|
|
162
|
+
if not venv:
|
|
163
|
+
# Fallback: if a virtual environment is already active, respect it
|
|
164
|
+
ve = os.environ.get("VIRTUAL_ENV")
|
|
165
|
+
if ve and is_venv_dir(Path(ve)):
|
|
166
|
+
return Path(ve)
|
|
167
|
+
raise EnvonError("No virtual environment found here. Create one (e.g., '.venv') or pass a path.")
|
|
168
|
+
return venv
|
|
169
|
+
|
|
170
|
+
p = Path(target)
|
|
171
|
+
if p.exists():
|
|
172
|
+
if p.is_dir() and is_venv_dir(p):
|
|
173
|
+
return p
|
|
174
|
+
# Allow passing project root; try common children
|
|
175
|
+
multiple = _list_venvs_in_dir(p)
|
|
176
|
+
if len(multiple) == 1:
|
|
177
|
+
return multiple[0]
|
|
178
|
+
if len(multiple) > 1:
|
|
179
|
+
return _choose_interactively(multiple, str(p))
|
|
180
|
+
raise EnvonError(f"Path does not appear to contain a virtual environment: {p}")
|
|
181
|
+
|
|
182
|
+
# Fallback: WORKON_HOME name
|
|
183
|
+
workon = os.environ.get("WORKON_HOME")
|
|
184
|
+
if workon:
|
|
185
|
+
cand = Path(workon) / target
|
|
186
|
+
if is_venv_dir(cand):
|
|
187
|
+
return cand
|
|
188
|
+
raise EnvonError(f"Cannot resolve virtual environment from argument: {target}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def detect_shell(explicit: str | None) -> str:
|
|
192
|
+
if explicit:
|
|
193
|
+
return explicit.lower()
|
|
194
|
+
|
|
195
|
+
# Heuristics by platform/env
|
|
196
|
+
if os.name == "nt":
|
|
197
|
+
# Prefer PowerShell if available, else default to cmd
|
|
198
|
+
if "PSModulePath" in os.environ:
|
|
199
|
+
return "powershell"
|
|
200
|
+
return "cmd"
|
|
201
|
+
# POSIX
|
|
202
|
+
# 1) Environment variables set by the shell itself (most reliable when present)
|
|
203
|
+
if os.environ.get("ZSH_VERSION"):
|
|
204
|
+
return "zsh"
|
|
205
|
+
if os.environ.get("BASH_VERSION"):
|
|
206
|
+
return "bash"
|
|
207
|
+
if os.environ.get("FISH_VERSION"):
|
|
208
|
+
return "fish"
|
|
209
|
+
# nushell does not (always) export a dedicated var; try a common one if present
|
|
210
|
+
if os.environ.get("NU_VERSION"):
|
|
211
|
+
return "nushell"
|
|
212
|
+
|
|
213
|
+
# 2) Inspect parent process (the shell) via /proc when available (Linux/WSL)
|
|
214
|
+
try:
|
|
215
|
+
ppid = os.getppid()
|
|
216
|
+
proc_comm = Path("/proc") / str(ppid) / "comm"
|
|
217
|
+
name = ""
|
|
218
|
+
if proc_comm.exists():
|
|
219
|
+
try:
|
|
220
|
+
name = proc_comm.read_text(encoding="utf-8").strip().lower()
|
|
221
|
+
except Exception:
|
|
222
|
+
name = ""
|
|
223
|
+
if not name:
|
|
224
|
+
proc_exe = Path("/proc") / str(ppid) / "exe"
|
|
225
|
+
if proc_exe.exists():
|
|
226
|
+
try:
|
|
227
|
+
name = os.path.basename(os.readlink(proc_exe)).lower()
|
|
228
|
+
except Exception:
|
|
229
|
+
name = ""
|
|
230
|
+
if name:
|
|
231
|
+
# Normalize common names
|
|
232
|
+
if "zsh" in name:
|
|
233
|
+
return "zsh"
|
|
234
|
+
if name in {"bash", "sh"} or "bash" in name:
|
|
235
|
+
return "bash" if "bash" in name else "sh"
|
|
236
|
+
if "fish" in name:
|
|
237
|
+
return "fish"
|
|
238
|
+
if name in {"csh", "tcsh"} or "csh" in name or "tcsh" in name:
|
|
239
|
+
return "cshell"
|
|
240
|
+
if "nu" in name:
|
|
241
|
+
return "nushell"
|
|
242
|
+
if name in {"pwsh", "powershell"}:
|
|
243
|
+
return "powershell"
|
|
244
|
+
if name in {"cmd", "cmd.exe"}:
|
|
245
|
+
return "cmd"
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
# 3) Fallback to $SHELL login shell
|
|
250
|
+
shell = os.environ.get("SHELL", "").lower()
|
|
251
|
+
if "zsh" in shell:
|
|
252
|
+
return "zsh"
|
|
253
|
+
if "fish" in shell:
|
|
254
|
+
return "fish"
|
|
255
|
+
if "csh" in shell or "tcsh" in shell:
|
|
256
|
+
return "cshell"
|
|
257
|
+
if "nu" in shell or "nushell" in shell:
|
|
258
|
+
return "nushell"
|
|
259
|
+
if shell.endswith("sh") and "bash" not in shell:
|
|
260
|
+
return "sh"
|
|
261
|
+
return "bash"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def emit_activation(venv: Path, shell: str) -> str:
|
|
265
|
+
"""Generate activation command using virtualenv's activation plugin system."""
|
|
266
|
+
shell = shell.lower()
|
|
267
|
+
|
|
268
|
+
# Nushell is not supported on Windows — refuse to emit activation for it
|
|
269
|
+
if os.name == "nt" and shell in {"nu", "nushell"}:
|
|
270
|
+
raise EnvonError(
|
|
271
|
+
"Nushell activation is not supported on Windows. Use PowerShell (powershell/pwsh) or cmd."
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Map shell names to activator entry point names
|
|
275
|
+
shell_to_activator = {
|
|
276
|
+
"bash": "bash",
|
|
277
|
+
"zsh": "bash", # zsh uses bash activator
|
|
278
|
+
"sh": "bash", # sh uses bash activator
|
|
279
|
+
"fish": "fish",
|
|
280
|
+
"csh": "cshell",
|
|
281
|
+
"tcsh": "cshell",
|
|
282
|
+
"cshell": "cshell",
|
|
283
|
+
"nu": "nushell", # Map nushell to its activator
|
|
284
|
+
"nushell": "nushell", # Map nushell to its activator
|
|
285
|
+
"powershell": "powershell",
|
|
286
|
+
"pwsh": "powershell",
|
|
287
|
+
"cmd": "batch",
|
|
288
|
+
"batch": "batch",
|
|
289
|
+
"bat": "batch",
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
activator_name = shell_to_activator.get(shell)
|
|
293
|
+
if not activator_name:
|
|
294
|
+
supported = ", ".join(sorted(shell_to_activator.keys()))
|
|
295
|
+
raise EnvonError(
|
|
296
|
+
f"Unsupported shell: {shell}. Supported shells: {supported}. "
|
|
297
|
+
f"Specify --emit <shell> explicitly or omit --emit to auto-detect."
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Try to use the plugin system to get proper script names
|
|
301
|
+
if PluginLoader:
|
|
302
|
+
try:
|
|
303
|
+
activators = PluginLoader.entry_points_for("virtualenv.activate")
|
|
304
|
+
if activator_name in activators:
|
|
305
|
+
activator_class = activators[activator_name]
|
|
306
|
+
|
|
307
|
+
# Create a minimal mock creator to get script names
|
|
308
|
+
class MockCreator:
|
|
309
|
+
def __init__(self, venv_path):
|
|
310
|
+
self.dest = venv_path
|
|
311
|
+
if (venv_path / "Scripts").exists(): # Windows
|
|
312
|
+
self.bin_dir = venv_path / "Scripts"
|
|
313
|
+
else: # POSIX
|
|
314
|
+
self.bin_dir = venv_path / "bin"
|
|
315
|
+
|
|
316
|
+
mock_creator = MockCreator(venv)
|
|
317
|
+
|
|
318
|
+
# Try to determine activation script name from the activator
|
|
319
|
+
try:
|
|
320
|
+
# Create a temporary activator instance with minimal options
|
|
321
|
+
class MockOptions:
|
|
322
|
+
prompt = None
|
|
323
|
+
|
|
324
|
+
activator = activator_class(MockOptions())
|
|
325
|
+
|
|
326
|
+
# Get the templates to determine script names
|
|
327
|
+
if hasattr(activator, 'templates'):
|
|
328
|
+
for template in activator.templates():
|
|
329
|
+
if hasattr(activator, 'as_name'):
|
|
330
|
+
script_name = activator.as_name(template)
|
|
331
|
+
else:
|
|
332
|
+
script_name = template
|
|
333
|
+
|
|
334
|
+
script_path = mock_creator.bin_dir / script_name
|
|
335
|
+
if script_path.exists():
|
|
336
|
+
return _generate_activation_command(script_path, shell)
|
|
337
|
+
except Exception:
|
|
338
|
+
# Fall back to hardcoded approach if activator instantiation fails
|
|
339
|
+
pass
|
|
340
|
+
except Exception:
|
|
341
|
+
# Fall back to hardcoded paths if plugin system fails
|
|
342
|
+
pass
|
|
343
|
+
|
|
344
|
+
# Fallback: Use hardcoded script detection
|
|
345
|
+
return _emit_activation_fallback(venv, shell)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _generate_activation_command(script_path: Path, shell: str) -> str:
|
|
349
|
+
"""Generate the appropriate activation command for the given script and shell."""
|
|
350
|
+
shell = shell.lower()
|
|
351
|
+
|
|
352
|
+
if shell in {"bash", "zsh", "sh"}:
|
|
353
|
+
return f". '{script_path.as_posix()}'"
|
|
354
|
+
elif shell == "fish":
|
|
355
|
+
return f"source '{script_path.as_posix()}'"
|
|
356
|
+
if shell in {"csh", "tcsh", "cshell"}:
|
|
357
|
+
return f"source {script_path.as_posix()}"
|
|
358
|
+
elif shell in {"nu", "nushell"}:
|
|
359
|
+
# For Nushell we only print the overlay use on the activation script path.
|
|
360
|
+
return f"overlay use \"{script_path.as_posix()}\""
|
|
361
|
+
elif shell in {"powershell", "pwsh"}:
|
|
362
|
+
return f". '{script_path.as_posix()}'"
|
|
363
|
+
elif shell in {"cmd", "batch", "bat"}:
|
|
364
|
+
return f"call \"{script_path}\""
|
|
365
|
+
|
|
366
|
+
raise EnvonError(f"Unknown shell command format for: {shell}")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _emit_activation_fallback(venv: Path, shell: str) -> str:
|
|
370
|
+
"""Fallback activation detection using hardcoded paths."""
|
|
371
|
+
shell = shell.lower()
|
|
372
|
+
|
|
373
|
+
if shell in {"bash", "zsh", "sh"}:
|
|
374
|
+
act = venv / "bin" / "activate"
|
|
375
|
+
if act.exists():
|
|
376
|
+
return f". '{act.as_posix()}'"
|
|
377
|
+
elif shell == "fish":
|
|
378
|
+
act = venv / "bin" / "activate.fish"
|
|
379
|
+
if act.exists():
|
|
380
|
+
return f"source '{act.as_posix()}'"
|
|
381
|
+
elif shell in {"csh", "tcsh", "cshell"}:
|
|
382
|
+
act = venv / "bin" / "activate.csh"
|
|
383
|
+
if act.exists():
|
|
384
|
+
return f"source {act.as_posix()}"
|
|
385
|
+
elif shell in {"nu", "nushell"}:
|
|
386
|
+
# Check for activate.nu in both Windows and POSIX locations and print overlay use on it.
|
|
387
|
+
act_posix = venv / "bin" / "activate.nu"
|
|
388
|
+
act_windows = venv / "Scripts" / "activate.nu"
|
|
389
|
+
act = act_posix if act_posix.exists() else act_windows if act_windows.exists() else None
|
|
390
|
+
if act and act.exists():
|
|
391
|
+
return f"overlay use \"{act.as_posix()}\""
|
|
392
|
+
raise EnvonError(
|
|
393
|
+
f"Virtual environment '{venv}' does not support Nushell activation: 'activate.nu' is missing. "
|
|
394
|
+
"Create or upgrade the environment with a tool that generates Nushell activation scripts, "
|
|
395
|
+
"or use a different shell (bash/zsh/fish)."
|
|
396
|
+
)
|
|
397
|
+
elif shell in {"powershell", "pwsh"}:
|
|
398
|
+
act = venv / "Scripts" / "Activate.ps1"
|
|
399
|
+
if act.exists():
|
|
400
|
+
return f". '{act.as_posix()}'"
|
|
401
|
+
elif shell in {"cmd", "batch", "bat"}:
|
|
402
|
+
act = venv / "Scripts" / "activate.bat"
|
|
403
|
+
if act.exists():
|
|
404
|
+
return f"call \"{act}\""
|
|
405
|
+
|
|
406
|
+
raise EnvonError(
|
|
407
|
+
f"No activation script found for shell '{shell}' in '{venv}'. "
|
|
408
|
+
"Try specifying --emit explicitly, or ensure the virtualenv's activation scripts exist."
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
## Nushell: uses overlay use on activate.nu directly
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
416
|
+
p = argparse.ArgumentParser(
|
|
417
|
+
prog="envon",
|
|
418
|
+
description="Emit the activation command for the nearest or specified virtual environment.",
|
|
419
|
+
)
|
|
420
|
+
p.add_argument("target", nargs="?", help="Path, project root, or name (searched in WORKON_HOME)")
|
|
421
|
+
p.add_argument(
|
|
422
|
+
"--emit",
|
|
423
|
+
nargs="?",
|
|
424
|
+
const="",
|
|
425
|
+
metavar="SHELL",
|
|
426
|
+
help=(
|
|
427
|
+
"Emit activation command. If SHELL is provided, use it (bash, zsh, sh, fish, cshell, nushell, powershell, pwsh, cmd); "
|
|
428
|
+
"if omitted, auto-detect the current shell."
|
|
429
|
+
),
|
|
430
|
+
)
|
|
431
|
+
p.add_argument(
|
|
432
|
+
"--print-path",
|
|
433
|
+
action="store_true",
|
|
434
|
+
help="Print only the resolved virtual environment path and exit.",
|
|
435
|
+
)
|
|
436
|
+
p.add_argument(
|
|
437
|
+
"--install",
|
|
438
|
+
nargs="?",
|
|
439
|
+
const="",
|
|
440
|
+
metavar="SHELL",
|
|
441
|
+
help=(
|
|
442
|
+
"Install envon bootstrap function directly to shell configuration file. "
|
|
443
|
+
"If SHELL is omitted, auto-detect."
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
return p.parse_args(argv)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def emit_bootstrap(shell: str) -> str:
|
|
450
|
+
"""Generate the bootstrap function for the given shell by reading from dedicated files."""
|
|
451
|
+
shell = shell.lower()
|
|
452
|
+
bootstrap_dir = Path(__file__).parent # Directory of envon.py
|
|
453
|
+
|
|
454
|
+
file_map = {
|
|
455
|
+
"bash": "bootstrap_bash.sh",
|
|
456
|
+
"zsh": "bootstrap_bash.sh", # zsh reuses bash
|
|
457
|
+
"sh": "bootstrap_sh.sh",
|
|
458
|
+
"fish": "bootstrap_fish.fish",
|
|
459
|
+
"nushell": "bootstrap_nushell.nu",
|
|
460
|
+
"nu": "bootstrap_nushell.nu",
|
|
461
|
+
"powershell": "bootstrap_powershell.ps1",
|
|
462
|
+
"pwsh": "bootstrap_powershell.ps1",
|
|
463
|
+
"csh": "bootstrap_csh.csh",
|
|
464
|
+
"tcsh": "bootstrap_csh.csh",
|
|
465
|
+
"cshell": "bootstrap_csh.csh",
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if shell not in file_map:
|
|
469
|
+
raise EnvonError(f"Unsupported shell: {shell}")
|
|
470
|
+
|
|
471
|
+
bootstrap_file = bootstrap_dir / file_map[shell]
|
|
472
|
+
if not bootstrap_file.exists():
|
|
473
|
+
raise EnvonError(f"Bootstrap file missing: {bootstrap_file}")
|
|
474
|
+
|
|
475
|
+
text = bootstrap_file.read_text(encoding="utf-8")
|
|
476
|
+
if text.startswith("\ufeff"): # strip BOM
|
|
477
|
+
text = text.lstrip("\ufeff")
|
|
478
|
+
return text
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def get_shell_config_path(shell: str) -> Path:
|
|
482
|
+
"""Get the configuration file path for a given shell."""
|
|
483
|
+
shell = shell.lower()
|
|
484
|
+
home = Path.home()
|
|
485
|
+
|
|
486
|
+
if shell == "bash":
|
|
487
|
+
# Try .bashrc first, fall back to .bash_profile
|
|
488
|
+
bashrc = home / ".bashrc"
|
|
489
|
+
if bashrc.exists():
|
|
490
|
+
return bashrc
|
|
491
|
+
return home / ".bash_profile"
|
|
492
|
+
if shell == "sh":
|
|
493
|
+
# POSIX sh typically sources ~/.profile (login shells); there is no standard per-shell rc
|
|
494
|
+
# We choose ~/.profile as the install target.
|
|
495
|
+
return home / ".profile"
|
|
496
|
+
elif shell == "zsh":
|
|
497
|
+
return home / ".zshrc"
|
|
498
|
+
elif shell == "fish":
|
|
499
|
+
config_dir = home / ".config" / "fish"
|
|
500
|
+
return config_dir / "config.fish"
|
|
501
|
+
elif shell in {"nushell", "nu"}:
|
|
502
|
+
if os.name == "nt": # Windows
|
|
503
|
+
config_dir = Path(os.environ.get("APPDATA", home)) / "nushell"
|
|
504
|
+
else: # POSIX
|
|
505
|
+
config_dir = home / ".config" / "nushell"
|
|
506
|
+
return config_dir / "config.nu"
|
|
507
|
+
elif shell in {"powershell", "pwsh"}:
|
|
508
|
+
if os.name == "nt": # Windows
|
|
509
|
+
documents = Path.home() / "Documents"
|
|
510
|
+
# Check for both possible profile file names
|
|
511
|
+
if shell == "pwsh":
|
|
512
|
+
core_profile = documents / "PowerShell" / "Microsoft.PowerShell_profile.ps1"
|
|
513
|
+
alt_core_profile = documents / "PowerShell" / "profile.ps1"
|
|
514
|
+
if core_profile.exists():
|
|
515
|
+
return core_profile
|
|
516
|
+
if alt_core_profile.exists():
|
|
517
|
+
return alt_core_profile
|
|
518
|
+
# Default to core_profile if neither exists
|
|
519
|
+
return core_profile
|
|
520
|
+
else:
|
|
521
|
+
win_profile = documents / "WindowsPowerShell" / "Microsoft.PowerShell_profile.ps1"
|
|
522
|
+
alt_win_profile = documents / "WindowsPowerShell" / "profile.ps1"
|
|
523
|
+
if win_profile.exists():
|
|
524
|
+
return win_profile
|
|
525
|
+
if alt_win_profile.exists():
|
|
526
|
+
return alt_win_profile
|
|
527
|
+
# Default to win_profile if neither exists
|
|
528
|
+
return win_profile
|
|
529
|
+
else: # POSIX PowerShell Core
|
|
530
|
+
return home / ".config" / "powershell" / "Microsoft.PowerShell_profile.ps1"
|
|
531
|
+
elif shell in {"csh", "tcsh", "cshell"}:
|
|
532
|
+
if shell == "tcsh":
|
|
533
|
+
return home / ".tcshrc"
|
|
534
|
+
return home / ".cshrc"
|
|
535
|
+
|
|
536
|
+
raise EnvonError(f"Unknown shell configuration path for: {shell}")
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def install_bootstrap(shell: str | None) -> str:
|
|
540
|
+
"""Install envon bootstrap function to shell configuration file."""
|
|
541
|
+
shell = detect_shell(shell) # auto-detect when None or empty string
|
|
542
|
+
shell = shell.lower()
|
|
543
|
+
# Windows-specific policy: do not modify any profile files automatically
|
|
544
|
+
# - Nushell is not supported on Windows for installation
|
|
545
|
+
# - For PowerShell, write the managed file and instruct the user to update their profile manually
|
|
546
|
+
if os.name == "nt":
|
|
547
|
+
if shell in {"nushell", "nu"}:
|
|
548
|
+
raise EnvonError(
|
|
549
|
+
"Nushell is not supported on Windows. Please use PowerShell (powershell/pwsh)."
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Default to PowerShell on Windows installs
|
|
553
|
+
ps_shell = "powershell" if shell == "powershell" else ("pwsh" if shell == "pwsh" else "powershell")
|
|
554
|
+
managed_file = get_managed_bootstrap_path(ps_shell)
|
|
555
|
+
managed_file.parent.mkdir(parents=True, exist_ok=True)
|
|
556
|
+
|
|
557
|
+
# Generate and write the managed content (PowerShell)
|
|
558
|
+
content = _managed_content_for_shell("powershell" if ps_shell == "powershell" else "powershell")
|
|
559
|
+
_write_managed_if_changed(managed_file, content)
|
|
560
|
+
|
|
561
|
+
# Compute the recommended profile path to show the user
|
|
562
|
+
profile_path = get_shell_config_path(ps_shell)
|
|
563
|
+
|
|
564
|
+
manual_block = (
|
|
565
|
+
f"$envonPath = '{managed_file}'\nif (Test-Path $envonPath) {{ . $envonPath }}"
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
return (
|
|
569
|
+
"envon bootstrap prepared for Windows (no profile auto-edit performed).\n"
|
|
570
|
+
f"- managed file: {managed_file}\n"
|
|
571
|
+
f"- PowerShell profile: {profile_path}\n\n"
|
|
572
|
+
"Add the following lines to your PowerShell profile manually, then restart the shell:\n\n"
|
|
573
|
+
f"{manual_block}"
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Non-Windows: proceed with automated RC update
|
|
577
|
+
config_path = get_shell_config_path(shell)
|
|
578
|
+
|
|
579
|
+
# Ensure parent directory exists and target is a file path
|
|
580
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
581
|
+
# Guard against a directory accidentally existing at the profile path
|
|
582
|
+
if config_path.exists() and config_path.is_dir():
|
|
583
|
+
raise EnvonError(
|
|
584
|
+
f"Profile path points to a directory, not a file: {config_path}. "
|
|
585
|
+
"Please remove/rename this directory or set the correct profile file."
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
# Managed bootstrap: write function to a stable file and source it from RC with markers
|
|
589
|
+
managed_file = get_managed_bootstrap_path(shell)
|
|
590
|
+
managed_file.parent.mkdir(parents=True, exist_ok=True)
|
|
591
|
+
|
|
592
|
+
# Generate function content for the managed file
|
|
593
|
+
target_shell = (
|
|
594
|
+
"bash" if shell == "bash" else
|
|
595
|
+
"zsh" if shell == "zsh" else
|
|
596
|
+
"sh" if shell == "sh" else
|
|
597
|
+
"fish" if shell == "fish" else
|
|
598
|
+
"nushell" if shell in {"nushell", "nu"} else
|
|
599
|
+
"powershell" if shell in {"powershell", "pwsh"} else
|
|
600
|
+
"csh" if shell in {"csh", "tcsh", "cshell"} else None
|
|
601
|
+
)
|
|
602
|
+
if target_shell is None:
|
|
603
|
+
supported = "bash, zsh, sh, fish, nushell, nu, powershell, pwsh, csh, tcsh, cshell"
|
|
604
|
+
raise EnvonError(
|
|
605
|
+
f"Unsupported shell for installation: {shell}. Supported: {supported}. "
|
|
606
|
+
f"Specify '--install <shell>' explicitly or run 'envon --bootstrap <shell>' and source it manually."
|
|
607
|
+
)
|
|
608
|
+
content = _managed_content_for_shell(target_shell)
|
|
609
|
+
_write_managed_if_changed(managed_file, content)
|
|
610
|
+
|
|
611
|
+
# Ensure RC contains a single, marked source block
|
|
612
|
+
_ensure_rc_sources_managed(config_path, managed_file, shell)
|
|
613
|
+
# Pick correct source command for user hint
|
|
614
|
+
source_cmd = (
|
|
615
|
+
"." if shell in {"sh", "powershell", "pwsh"} else "source"
|
|
616
|
+
)
|
|
617
|
+
return (
|
|
618
|
+
f"envon bootstrap installed:\n- managed: {managed_file}\n- rc: {config_path}\n"
|
|
619
|
+
f"Restart your shell or run: {source_cmd} {config_path}"
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
MARK_START = "# >>> envon bootstrap >>>"
|
|
624
|
+
MARK_END = "# <<< envon bootstrap <<<"
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def get_managed_bootstrap_path(shell: str) -> Path:
|
|
628
|
+
"""Return the managed bootstrap file path for a shell."""
|
|
629
|
+
shell = shell.lower()
|
|
630
|
+
# Determine config base dir
|
|
631
|
+
if os.name == "nt":
|
|
632
|
+
base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
|
|
633
|
+
else:
|
|
634
|
+
base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
|
|
635
|
+
envon_dir = base / "envon"
|
|
636
|
+
|
|
637
|
+
name = (
|
|
638
|
+
"envon.bash" if shell == "bash" else
|
|
639
|
+
"envon.zsh" if shell == "zsh" else
|
|
640
|
+
"envon.sh" if shell == "sh" else
|
|
641
|
+
"envon.fish" if shell == "fish" else
|
|
642
|
+
"envon.nu" if shell in {"nushell", "nu"} else
|
|
643
|
+
"envon.ps1" if shell in {"powershell", "pwsh"} else
|
|
644
|
+
"envon.csh" if shell in {"csh", "tcsh", "cshell"} else None
|
|
645
|
+
)
|
|
646
|
+
if name is None:
|
|
647
|
+
raise EnvonError(f"Unsupported shell: {shell}")
|
|
648
|
+
return envon_dir / name
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
# def get_nushell_venv_path() -> Path:
|
|
652
|
+
# """Return the path to the managed Nushell venv.nu file."""
|
|
653
|
+
# if os.name == "nt":
|
|
654
|
+
# base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
|
|
655
|
+
# else:
|
|
656
|
+
# base = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
|
|
657
|
+
# envon_dir = base / "envon"
|
|
658
|
+
# return envon_dir / "venv.nu"
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
# def _ensure_nushell_venv_file() -> None:
|
|
662
|
+
# """Ensure the managed Nushell venv.nu file and parent dir exist."""
|
|
663
|
+
# venv_path = get_nushell_venv_path()
|
|
664
|
+
# venv_path.parent.mkdir(parents=True, exist_ok=True)
|
|
665
|
+
# if not venv_path.exists():
|
|
666
|
+
# # create a placeholder file that will be overwritten on activation
|
|
667
|
+
# venv_path.write_text("# envon venv.nu - will be overwritten when activating environments\n", encoding="utf-8")
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _write_managed_if_changed(path: Path, content: str) -> None:
|
|
671
|
+
"""Write content to path if missing or different."""
|
|
672
|
+
try:
|
|
673
|
+
if path.exists() and path.read_text() == content:
|
|
674
|
+
return
|
|
675
|
+
except Exception:
|
|
676
|
+
# If read fails, attempt to overwrite
|
|
677
|
+
pass
|
|
678
|
+
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
679
|
+
tmp.write_text(content, encoding="utf-8")
|
|
680
|
+
tmp.replace(path)
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def _ensure_rc_sources_managed(config_path: Path, managed_file: Path, shell: str) -> None:
|
|
684
|
+
"""Ensure the user's RC/profile sources the managed file, using idempotent markers."""
|
|
685
|
+
rc_exists = config_path.exists() and config_path.is_file()
|
|
686
|
+
rc_text = config_path.read_text(encoding="utf-8") if rc_exists else ""
|
|
687
|
+
|
|
688
|
+
# If already installed with markers, do nothing
|
|
689
|
+
if MARK_START in rc_text and MARK_END in rc_text:
|
|
690
|
+
return
|
|
691
|
+
|
|
692
|
+
mf = managed_file.as_posix()
|
|
693
|
+
if shell in {"bash", "zsh", "sh"}:
|
|
694
|
+
block = f"\n{MARK_START}\n[ -f {mf} ] && . {mf}\n{MARK_END}\n"
|
|
695
|
+
elif shell == "fish":
|
|
696
|
+
block = f"\n{MARK_START}\nif test -f {mf}\n source {mf}\nend\n{MARK_END}\n"
|
|
697
|
+
elif shell in {"nushell", "nu"}:
|
|
698
|
+
# Always quote the managed file path to avoid extra positional argument errors
|
|
699
|
+
block = (
|
|
700
|
+
f"\n{MARK_START}\n"
|
|
701
|
+
f"if (ls '{mf}' | is-empty) == false {{\n source '{mf}'\n}}\n"
|
|
702
|
+
f"{MARK_END}\n"
|
|
703
|
+
)
|
|
704
|
+
elif shell in {"powershell", "pwsh"}:
|
|
705
|
+
block = (
|
|
706
|
+
f"\n{MARK_START}\n"
|
|
707
|
+
f"$envonPath = '{managed_file}'\nif (Test-Path $envonPath) {{ . $envonPath }}\n"
|
|
708
|
+
f"{MARK_END}\n"
|
|
709
|
+
)
|
|
710
|
+
elif shell in {"csh", "tcsh", "cshell"}:
|
|
711
|
+
block = f"\n{MARK_START}\nif ( -f {mf} ) source {mf}\n{MARK_END}\n"
|
|
712
|
+
else:
|
|
713
|
+
raise EnvonError(f"Unsupported shell: {shell}")
|
|
714
|
+
|
|
715
|
+
# Write or append the block with a robust fallback for Windows I/O quirks
|
|
716
|
+
try:
|
|
717
|
+
# Try to clear read-only flag if present
|
|
718
|
+
if rc_exists:
|
|
719
|
+
try:
|
|
720
|
+
os.chmod(config_path, stat.S_IWRITE | stat.S_IREAD)
|
|
721
|
+
except Exception:
|
|
722
|
+
pass
|
|
723
|
+
if not rc_exists:
|
|
724
|
+
# Create new profile file with our block
|
|
725
|
+
config_path.write_text(block, encoding="utf-8")
|
|
726
|
+
else:
|
|
727
|
+
with config_path.open("a", encoding="utf-8") as f:
|
|
728
|
+
f.write(block)
|
|
729
|
+
except OSError as e:
|
|
730
|
+
# Fallback: write the full combined content (existing + block)
|
|
731
|
+
combined = rc_text + block
|
|
732
|
+
try:
|
|
733
|
+
config_path.write_text(combined, encoding="utf-8")
|
|
734
|
+
except Exception as e2:
|
|
735
|
+
# On PowerShell, also try the alternate profile file name
|
|
736
|
+
if shell in {"powershell", "pwsh"}:
|
|
737
|
+
try:
|
|
738
|
+
alt_name = (
|
|
739
|
+
"Microsoft.PowerShell_profile.ps1"
|
|
740
|
+
if config_path.name.lower() == "profile.ps1"
|
|
741
|
+
else "profile.ps1"
|
|
742
|
+
)
|
|
743
|
+
alt_path = config_path.with_name(alt_name)
|
|
744
|
+
alt_path.parent.mkdir(parents=True, exist_ok=True)
|
|
745
|
+
try:
|
|
746
|
+
# Clear read-only if exists
|
|
747
|
+
if alt_path.exists():
|
|
748
|
+
try:
|
|
749
|
+
os.chmod(alt_path, stat.S_IWRITE | stat.S_IREAD)
|
|
750
|
+
except Exception:
|
|
751
|
+
pass
|
|
752
|
+
# If alternate exists and already contains our block, we're done
|
|
753
|
+
try:
|
|
754
|
+
alt_text = alt_path.read_text(encoding="utf-8")
|
|
755
|
+
except Exception:
|
|
756
|
+
alt_text = ""
|
|
757
|
+
if MARK_START in alt_text and MARK_END in alt_text:
|
|
758
|
+
return
|
|
759
|
+
# Otherwise write combined content to alternate profile
|
|
760
|
+
alt_combined = alt_text + block if alt_text else block
|
|
761
|
+
alt_path.write_text(alt_combined, encoding="utf-8")
|
|
762
|
+
return
|
|
763
|
+
except Exception as e3:
|
|
764
|
+
raise EnvonError(
|
|
765
|
+
"Failed to update PowerShell profile. Tried both: "
|
|
766
|
+
f"{config_path} (error: {e2}) and {alt_path} (error: {e3}). "
|
|
767
|
+
"Close any editor locking the file, ensure it's not a directory or read-only, "
|
|
768
|
+
"or create the file manually and re-run."
|
|
769
|
+
) from e3
|
|
770
|
+
except Exception:
|
|
771
|
+
# If building alt path failed, fall through to generic error
|
|
772
|
+
pass
|
|
773
|
+
# Generic failure if all fallbacks failed
|
|
774
|
+
raise EnvonError(
|
|
775
|
+
f"Failed to update shell profile at {config_path}: {e2}"
|
|
776
|
+
) from e
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def _managed_content_for_shell(shell: str) -> str:
|
|
780
|
+
"""Build the content stored in the managed file, tagged with the package version.
|
|
781
|
+
|
|
782
|
+
Including the version allows us to detect when an upgrade may require refreshing
|
|
783
|
+
the managed file, while avoiding unnecessary rewrites.
|
|
784
|
+
"""
|
|
785
|
+
body = emit_bootstrap(shell)
|
|
786
|
+
header = f"# envon managed bootstrap - version: {VENV_VERSION}\n"
|
|
787
|
+
return header + body
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def _maybe_update_managed_current_shell(explicit_shell: str | None) -> None:
|
|
791
|
+
"""If a managed bootstrap file exists for the current/detected shell, refresh it when outdated.
|
|
792
|
+
|
|
793
|
+
This runs silently on each invocation and only writes when the content differs,
|
|
794
|
+
so normal runs stay fast and side-effect free for already up-to-date installs.
|
|
795
|
+
"""
|
|
796
|
+
try:
|
|
797
|
+
shell = detect_shell(explicit_shell)
|
|
798
|
+
managed = get_managed_bootstrap_path(shell)
|
|
799
|
+
if managed.exists():
|
|
800
|
+
desired = _managed_content_for_shell(
|
|
801
|
+
"bash" if shell == "bash" else
|
|
802
|
+
"zsh" if shell == "zsh" else
|
|
803
|
+
"sh" if shell == "sh" else
|
|
804
|
+
"fish" if shell == "fish" else
|
|
805
|
+
"nushell" if shell in {"nushell", "nu"} else
|
|
806
|
+
"powershell" if shell in {"powershell", "pwsh"} else
|
|
807
|
+
"csh" if shell in {"csh", "tcsh", "cshell"} else shell
|
|
808
|
+
)
|
|
809
|
+
try:
|
|
810
|
+
current = managed.read_text(encoding="utf-8")
|
|
811
|
+
except Exception:
|
|
812
|
+
current = ""
|
|
813
|
+
if current != desired:
|
|
814
|
+
_write_managed_if_changed(managed, desired)
|
|
815
|
+
except Exception:
|
|
816
|
+
# Never fail the main command due to a managed-file refresh issue
|
|
817
|
+
pass
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def main(argv: list[str] | None = None) -> int:
|
|
821
|
+
ns = parse_args(argv or sys.argv[1:])
|
|
822
|
+
try:
|
|
823
|
+
# Opportunistic refresh of managed bootstrap (no-op if not installed)
|
|
824
|
+
_maybe_update_managed_current_shell(None)
|
|
825
|
+
if ns.install is not None:
|
|
826
|
+
result = install_bootstrap(ns.install)
|
|
827
|
+
print(result)
|
|
828
|
+
return 0
|
|
829
|
+
venv = resolve_target(ns.target)
|
|
830
|
+
if ns.print_path:
|
|
831
|
+
print(str(venv))
|
|
832
|
+
return 0
|
|
833
|
+
shell = detect_shell(ns.emit)
|
|
834
|
+
cmd = emit_activation(venv, shell)
|
|
835
|
+
print(cmd)
|
|
836
|
+
return 0
|
|
837
|
+
except EnvonError as e:
|
|
838
|
+
print(str(e), file=sys.stderr)
|
|
839
|
+
return 2
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
if __name__ == "__main__": # pragma: no cover
|
|
843
|
+
raise SystemExit(main())
|