pyesh 1.0.0__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.
- pyesh-1.0.0/LICENSE +21 -0
- pyesh-1.0.0/PKG-INFO +110 -0
- pyesh-1.0.0/README.md +88 -0
- pyesh-1.0.0/pyproject.toml +46 -0
- pyesh-1.0.0/setup.cfg +4 -0
- pyesh-1.0.0/src/pyesh/__init__.py +6 -0
- pyesh-1.0.0/src/pyesh/__main__.py +8 -0
- pyesh-1.0.0/src/pyesh/_builtin_child.py +24 -0
- pyesh-1.0.0/src/pyesh/_job_child.py +25 -0
- pyesh-1.0.0/src/pyesh/api.py +246 -0
- pyesh-1.0.0/src/pyesh/backends.py +247 -0
- pyesh-1.0.0/src/pyesh/builtins.py +503 -0
- pyesh-1.0.0/src/pyesh/cli.py +192 -0
- pyesh-1.0.0/src/pyesh/config.py +60 -0
- pyesh-1.0.0/src/pyesh/console.py +270 -0
- pyesh-1.0.0/src/pyesh/execution.py +451 -0
- pyesh-1.0.0/src/pyesh/help.py +129 -0
- pyesh-1.0.0/src/pyesh/history.py +61 -0
- pyesh-1.0.0/src/pyesh/jobs.py +371 -0
- pyesh-1.0.0/src/pyesh/parsing.py +315 -0
- pyesh-1.0.0/src/pyesh/plugins.py +229 -0
- pyesh-1.0.0/src/pyesh/profiles.py +231 -0
- pyesh-1.0.0/src/pyesh/prompt.py +137 -0
- pyesh-1.0.0/src/pyesh/python_pipes.py +106 -0
- pyesh-1.0.0/src/pyesh/python_runtime.py +170 -0
- pyesh-1.0.0/src/pyesh/shell.py +1083 -0
- pyesh-1.0.0/src/pyesh/terminal.py +350 -0
- pyesh-1.0.0/src/pyesh/user_files.py +336 -0
- pyesh-1.0.0/src/pyesh.egg-info/PKG-INFO +110 -0
- pyesh-1.0.0/src/pyesh.egg-info/SOURCES.txt +32 -0
- pyesh-1.0.0/src/pyesh.egg-info/dependency_links.txt +1 -0
- pyesh-1.0.0/src/pyesh.egg-info/entry_points.txt +2 -0
- pyesh-1.0.0/src/pyesh.egg-info/requires.txt +9 -0
- pyesh-1.0.0/src/pyesh.egg-info/top_level.txt +1 -0
pyesh-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PiSaucer
|
|
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.
|
pyesh-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyesh
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Cross-platform, Python-oriented interactive and scripting shell.
|
|
5
|
+
Author: PiSaucer
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pisaucer/pyesh
|
|
8
|
+
Project-URL: Repository, https://github.com/PiSaucer/pyesh
|
|
9
|
+
Project-URL: Issues, https://github.com/PiSaucer/pyesh/issues
|
|
10
|
+
Keywords: cli,shell,interactive,scripting,python
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: prompt-toolkit<4,>=3.0.52
|
|
15
|
+
Requires-Dist: rich<15,>=13.7
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: build<2,>=1; extra == "dev"
|
|
18
|
+
Requires-Dist: twine<7,>=5; extra == "dev"
|
|
19
|
+
Provides-Extra: windows-executable
|
|
20
|
+
Requires-Dist: pyinstaller<7,>=6; extra == "windows-executable"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# pyesh
|
|
24
|
+
|
|
25
|
+
Python Expanded Shell: a cross-platform, Python-oriented interactive and scripting shell with direct process execution, typed Python pipelines, configurable user files, and command plugins.
|
|
26
|
+
|
|
27
|
+
pyesh is its own shell language rather than a Bash interpreter. Explicit `.sh` and `.ps1` files continue to run through installed Bash and PowerShell runtimes.
|
|
28
|
+
|
|
29
|
+
## Highlights
|
|
30
|
+
|
|
31
|
+
* Job tracking with `jobs`, `wait`, `fg`, `bg`, `kill`, and `disown`; POSIX terminal job control.
|
|
32
|
+
* Environment, aliases, history, directory stacks, portable output, and input built-ins.
|
|
33
|
+
* Pipelines, conditions, per-stage redirection, heredocs, and `pipefail`.
|
|
34
|
+
* Session variables, special parameters, command substitution, home expansion, and globs.
|
|
35
|
+
* Stateful native Python at the prompt.
|
|
36
|
+
* Typed `@variable:datatype` pipeline input and capture.
|
|
37
|
+
* Explicit Python, Bash, and PowerShell script dispatch.
|
|
38
|
+
* `source`/`deactivate` support for Python virtual environments.
|
|
39
|
+
* Colorful live syntax highlighting, arrow-key history, and Tab completion.
|
|
40
|
+
* Custom prompt, environment, PATH, startup, history, and plugin files.
|
|
41
|
+
* Named JSON profiles for complete terminal and session customization.
|
|
42
|
+
* Persistent Rich-powered customizable welcome splash.
|
|
43
|
+
* Active-venv Python selection and optional venv-local pyesh startup files.
|
|
44
|
+
* Stateful Python automation API.
|
|
45
|
+
|
|
46
|
+
## Install and run
|
|
47
|
+
|
|
48
|
+
Requires Python 3.9 or newer:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python -m pip install -e .
|
|
52
|
+
pyesh --init
|
|
53
|
+
pyesh
|
|
54
|
+
pyesh -c 'echo "hello $(printf world)"'
|
|
55
|
+
pyesh automation.pyesh one two
|
|
56
|
+
printf 'echo from-stdin\n' | pyesh -
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`pyesh` and `python -m pyesh` are equivalent.
|
|
60
|
+
|
|
61
|
+
```plaintext
|
|
62
|
+
user@machine project (main) % echo "hello"
|
|
63
|
+
hello
|
|
64
|
+
user@machine project (main) % numbers = [1, 2, 3]
|
|
65
|
+
user@machine project (main) % @numbers:lines | @copy:lines
|
|
66
|
+
user@machine project (main) % copy
|
|
67
|
+
['1', '2', '3']
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run `help` or `man` inside the shell. Use `pyesh --help` for command-line options and `pyesh --version` for package and interpreter locations. Run `welcome` for a customizable splash summarizing the current runtime and session. Configure its template and automatic startup in `~/.pyesh_welcome`.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
The [documentation](docs/README.md) links the complete project guides. See the [Changelog](CHANGELOG.md) and [development guide](docs/development.md) for project history and development workflow.
|
|
75
|
+
|
|
76
|
+
## Quick examples
|
|
77
|
+
|
|
78
|
+
Operators and scripts:
|
|
79
|
+
|
|
80
|
+
```plaintext
|
|
81
|
+
./examples/hello.py
|
|
82
|
+
./examples/hello.sh | grep Bash
|
|
83
|
+
failing-command || echo fallback
|
|
84
|
+
echo first > output.txt ; echo second >> output.txt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Virtual environment:
|
|
88
|
+
|
|
89
|
+
```plaintext
|
|
90
|
+
python -m venv .venv
|
|
91
|
+
source .venv/bin/activate
|
|
92
|
+
deactivate
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Python automation:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from pyesh import PyeshSession
|
|
99
|
+
|
|
100
|
+
session = PyeshSession(verbose=True)
|
|
101
|
+
status = session.run_argv(["python", "./examples/hello.py"])
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Language boundary
|
|
105
|
+
|
|
106
|
+
Python syntax supplies control flow and reusable functions. Bash functions, Bash parameter-expansion extensions, process substitution, and arbitrary file descriptor manipulation are not implemented. POSIX interactive job control is supported; Windows cannot suspend/resume jobs. Plugin capabilities beyond commands remain planned.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
Distributed under the **MIT License**. See [LICENSE](LICENSE) for more information.
|
pyesh-1.0.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# pyesh
|
|
2
|
+
|
|
3
|
+
Python Expanded Shell: a cross-platform, Python-oriented interactive and scripting shell with direct process execution, typed Python pipelines, configurable user files, and command plugins.
|
|
4
|
+
|
|
5
|
+
pyesh is its own shell language rather than a Bash interpreter. Explicit `.sh` and `.ps1` files continue to run through installed Bash and PowerShell runtimes.
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
8
|
+
|
|
9
|
+
* Job tracking with `jobs`, `wait`, `fg`, `bg`, `kill`, and `disown`; POSIX terminal job control.
|
|
10
|
+
* Environment, aliases, history, directory stacks, portable output, and input built-ins.
|
|
11
|
+
* Pipelines, conditions, per-stage redirection, heredocs, and `pipefail`.
|
|
12
|
+
* Session variables, special parameters, command substitution, home expansion, and globs.
|
|
13
|
+
* Stateful native Python at the prompt.
|
|
14
|
+
* Typed `@variable:datatype` pipeline input and capture.
|
|
15
|
+
* Explicit Python, Bash, and PowerShell script dispatch.
|
|
16
|
+
* `source`/`deactivate` support for Python virtual environments.
|
|
17
|
+
* Colorful live syntax highlighting, arrow-key history, and Tab completion.
|
|
18
|
+
* Custom prompt, environment, PATH, startup, history, and plugin files.
|
|
19
|
+
* Named JSON profiles for complete terminal and session customization.
|
|
20
|
+
* Persistent Rich-powered customizable welcome splash.
|
|
21
|
+
* Active-venv Python selection and optional venv-local pyesh startup files.
|
|
22
|
+
* Stateful Python automation API.
|
|
23
|
+
|
|
24
|
+
## Install and run
|
|
25
|
+
|
|
26
|
+
Requires Python 3.9 or newer:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python -m pip install -e .
|
|
30
|
+
pyesh --init
|
|
31
|
+
pyesh
|
|
32
|
+
pyesh -c 'echo "hello $(printf world)"'
|
|
33
|
+
pyesh automation.pyesh one two
|
|
34
|
+
printf 'echo from-stdin\n' | pyesh -
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`pyesh` and `python -m pyesh` are equivalent.
|
|
38
|
+
|
|
39
|
+
```plaintext
|
|
40
|
+
user@machine project (main) % echo "hello"
|
|
41
|
+
hello
|
|
42
|
+
user@machine project (main) % numbers = [1, 2, 3]
|
|
43
|
+
user@machine project (main) % @numbers:lines | @copy:lines
|
|
44
|
+
user@machine project (main) % copy
|
|
45
|
+
['1', '2', '3']
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Run `help` or `man` inside the shell. Use `pyesh --help` for command-line options and `pyesh --version` for package and interpreter locations. Run `welcome` for a customizable splash summarizing the current runtime and session. Configure its template and automatic startup in `~/.pyesh_welcome`.
|
|
49
|
+
|
|
50
|
+
## Documentation
|
|
51
|
+
|
|
52
|
+
The [documentation](docs/README.md) links the complete project guides. See the [Changelog](CHANGELOG.md) and [development guide](docs/development.md) for project history and development workflow.
|
|
53
|
+
|
|
54
|
+
## Quick examples
|
|
55
|
+
|
|
56
|
+
Operators and scripts:
|
|
57
|
+
|
|
58
|
+
```plaintext
|
|
59
|
+
./examples/hello.py
|
|
60
|
+
./examples/hello.sh | grep Bash
|
|
61
|
+
failing-command || echo fallback
|
|
62
|
+
echo first > output.txt ; echo second >> output.txt
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Virtual environment:
|
|
66
|
+
|
|
67
|
+
```plaintext
|
|
68
|
+
python -m venv .venv
|
|
69
|
+
source .venv/bin/activate
|
|
70
|
+
deactivate
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Python automation:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from pyesh import PyeshSession
|
|
77
|
+
|
|
78
|
+
session = PyeshSession(verbose=True)
|
|
79
|
+
status = session.run_argv(["python", "./examples/hello.py"])
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Language boundary
|
|
83
|
+
|
|
84
|
+
Python syntax supplies control flow and reusable functions. Bash functions, Bash parameter-expansion extensions, process substitution, and arbitrary file descriptor manipulation are not implemented. POSIX interactive job control is supported; Windows cannot suspend/resume jobs. Plugin capabilities beyond commands remain planned.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
Distributed under the **MIT License**. See [LICENSE](LICENSE) for more information.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyesh"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Cross-platform, Python-oriented interactive and scripting shell."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "PiSaucer" },
|
|
11
|
+
]
|
|
12
|
+
keywords = ["cli", "shell", "interactive", "scripting", "python"]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"prompt-toolkit>=3.0.52,<4",
|
|
15
|
+
"rich>=13.7,<15",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = [
|
|
20
|
+
"build>=1,<2",
|
|
21
|
+
"twine>=5,<7",
|
|
22
|
+
]
|
|
23
|
+
windows-executable = [
|
|
24
|
+
"pyinstaller>=6,<7",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/pisaucer/pyesh"
|
|
29
|
+
Repository = "https://github.com/PiSaucer/pyesh"
|
|
30
|
+
Issues = "https://github.com/PiSaucer/pyesh/issues"
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["setuptools>=77.0", "wheel"]
|
|
34
|
+
build-backend = "setuptools.build_meta"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
pyesh = "pyesh.cli:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.dynamic]
|
|
40
|
+
version = {attr = "pyesh.__version__"}
|
|
41
|
+
|
|
42
|
+
[tool.setuptools]
|
|
43
|
+
include-package-data = true
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
pyesh-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# _builtin_child.py
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
# Resolve the installed/source package beside this file, independent of cwd.
|
|
8
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
9
|
+
from pyesh.builtins import run_builtin
|
|
10
|
+
from pyesh.plugins import PluginManager
|
|
11
|
+
|
|
12
|
+
def main(arguments: Optional[List[str]] = None) -> Optional[int]:
|
|
13
|
+
"""Run a stateless built-in or an explicitly selected plugin command."""
|
|
14
|
+
values = sys.argv[1:] if arguments is None else arguments
|
|
15
|
+
if len(values) >= 3 and values[0] == "--plugin":
|
|
16
|
+
manager = PluginManager()
|
|
17
|
+
manager.load_enabled([values[1]])
|
|
18
|
+
return manager.run(values[2:]) if not manager.errors else 1
|
|
19
|
+
if not values or values[0] not in ("echo", "printf", "pwd"):
|
|
20
|
+
return 2
|
|
21
|
+
return run_builtin(values)
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# _job_child.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
def main() -> None:
|
|
8
|
+
"""Configure the requested process group and execute the child.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
None. A successful call replaces the helper process.
|
|
12
|
+
"""
|
|
13
|
+
group, error_fd = int(sys.argv[1]), int(sys.argv[2])
|
|
14
|
+
os.set_inheritable(error_fd, False)
|
|
15
|
+
try:
|
|
16
|
+
os.setpgid(0, group)
|
|
17
|
+
for name in ("SIGINT", "SIGQUIT", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGPIPE"):
|
|
18
|
+
signal.signal(getattr(signal, name), signal.SIG_DFL)
|
|
19
|
+
os.execvpe(sys.argv[3], sys.argv[3:], os.environ)
|
|
20
|
+
except OSError as error:
|
|
21
|
+
os.write(error_fd, str(error.errno).encode("ascii"))
|
|
22
|
+
os._exit(127)
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
main()
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# api.py
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Iterable, Mapping, Optional, Sequence
|
|
5
|
+
from .execution import run_pipeline
|
|
6
|
+
from .parsing import Command
|
|
7
|
+
from .shell import SessionState, execute_command_line, execute_script
|
|
8
|
+
|
|
9
|
+
class PyeshSession:
|
|
10
|
+
"""A reusable command-execution session for Python applications.
|
|
11
|
+
|
|
12
|
+
The session preserves controls changed by built-ins, such as verbose mode,
|
|
13
|
+
across calls. Commands use the hosting Python process's working directory,
|
|
14
|
+
environment, and standard streams.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, verbose: bool = False, plugins: Optional[Iterable[str]] = None) -> None:
|
|
17
|
+
"""Create an automation session.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
verbose: Whether to trace resolved built-ins and process launches.
|
|
21
|
+
plugins: Installed plugin entry-point names to load explicitly.
|
|
22
|
+
"""
|
|
23
|
+
self._state = SessionState(verbose=verbose)
|
|
24
|
+
if plugins is not None:
|
|
25
|
+
self._state.plugins.load_enabled(plugins)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def plugin_errors(self) -> Mapping[str, str]:
|
|
29
|
+
"""Return plugin loading failures keyed by plugin name.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
A copy of loading error messages.
|
|
33
|
+
"""
|
|
34
|
+
return self._state.plugins.errors
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def verbose(self) -> bool:
|
|
38
|
+
"""Return whether execution tracing is enabled.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The current verbose setting.
|
|
42
|
+
"""
|
|
43
|
+
return self._state.verbose
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def exit_requested(self) -> bool:
|
|
47
|
+
"""Return whether the session received the ``exit`` built-in.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
``True`` after an exit request.
|
|
51
|
+
"""
|
|
52
|
+
return self._state.exit_requested
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def exit_status(self) -> int:
|
|
56
|
+
"""Return the status retained by an explicit exit request.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The normalized process exit status.
|
|
60
|
+
"""
|
|
61
|
+
return self._state.exit_status
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def cwd(self) -> Path:
|
|
65
|
+
"""Return the session working directory.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The absolute session working directory.
|
|
69
|
+
"""
|
|
70
|
+
return self._state.cwd
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def environment(self) -> Mapping[str, str]:
|
|
74
|
+
"""Return a copy of the session child environment.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Environment variables keyed by name.
|
|
78
|
+
"""
|
|
79
|
+
return dict(self._state.environment)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def variables(self) -> Mapping[str, object]:
|
|
83
|
+
"""Return a copy of user-visible session variables.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Python-backed values keyed by variable name.
|
|
87
|
+
"""
|
|
88
|
+
return {key: value for key, value in self._state.python.namespace.items()
|
|
89
|
+
if not key.startswith("__") and key not in ("sh", "capture", "env", "argv")}
|
|
90
|
+
|
|
91
|
+
def set_cwd(self, path) -> None:
|
|
92
|
+
"""Set the isolated session working directory.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
path: Existing directory to use.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
None.
|
|
99
|
+
"""
|
|
100
|
+
candidate = Path(path).expanduser().resolve(strict=True)
|
|
101
|
+
if not candidate.is_dir():
|
|
102
|
+
raise NotADirectoryError(str(candidate))
|
|
103
|
+
self._state.cwd = candidate
|
|
104
|
+
self._state.environment["PWD"] = str(candidate)
|
|
105
|
+
|
|
106
|
+
def set_environment(self, name: str, value: Optional[str]) -> None:
|
|
107
|
+
"""Set or remove one session environment variable.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
name: Environment variable name.
|
|
111
|
+
value: String value, or ``None`` to remove the name.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
None.
|
|
115
|
+
"""
|
|
116
|
+
if value is None:
|
|
117
|
+
self._state.environment.pop(name, None)
|
|
118
|
+
else:
|
|
119
|
+
self._state.environment[name] = str(value)
|
|
120
|
+
|
|
121
|
+
def run(self, command_line: str) -> int:
|
|
122
|
+
"""Execute one pyesh command line.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
command_line: Commands and supported pyesh operators to execute.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The final executed job's exit status. The ``exit`` built-in returns
|
|
129
|
+
the requested status and sets ``exit_requested``.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
ValueError: If the command line has invalid syntax.
|
|
133
|
+
"""
|
|
134
|
+
if self._state.exit_requested:
|
|
135
|
+
return self._state.exit_status
|
|
136
|
+
return execute_command_line(command_line, state=self._state)
|
|
137
|
+
|
|
138
|
+
def run_all(self, command_lines: Iterable[str], stop_on_error: bool = False) -> int:
|
|
139
|
+
"""Execute command lines in order.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
command_lines: Command lines to execute.
|
|
143
|
+
stop_on_error: Stop after the first nonzero status when true.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
The final executed status, or zero for an empty iterable. Execution
|
|
147
|
+
also stops when a command requests session exit.
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
ValueError: If any command line has invalid syntax.
|
|
151
|
+
"""
|
|
152
|
+
status = 0
|
|
153
|
+
for command_line in command_lines:
|
|
154
|
+
status = self.run(command_line)
|
|
155
|
+
if self.exit_requested or (stop_on_error and status != 0):
|
|
156
|
+
break
|
|
157
|
+
return status
|
|
158
|
+
|
|
159
|
+
def run_script(self, source: str, filename: str = "<string>", argv: Iterable[str] = ()) -> int:
|
|
160
|
+
"""Execute native pyesh source in this session.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
source: Complete pyesh source text.
|
|
164
|
+
filename: Diagnostic name and ``$0`` value.
|
|
165
|
+
argv: Positional arguments exposed through ``argv`` and ``$1`` onward.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
The final command or explicit exit status.
|
|
169
|
+
"""
|
|
170
|
+
self._state.argv0 = filename
|
|
171
|
+
self._state.argv[:] = list(argv)
|
|
172
|
+
return execute_script(source, self._state, filename)
|
|
173
|
+
|
|
174
|
+
def run_file(self, path, argv: Iterable[str] = ()) -> int:
|
|
175
|
+
"""Read and execute a UTF-8 pyesh script.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
path: Script path.
|
|
179
|
+
argv: Positional script arguments.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The final command or explicit exit status.
|
|
183
|
+
"""
|
|
184
|
+
source_path = Path(path).expanduser().resolve(strict=True)
|
|
185
|
+
return self.run_script(source_path.read_text(encoding="utf-8"), str(source_path), argv)
|
|
186
|
+
|
|
187
|
+
def run_argv(self, arguments: Sequence[str]) -> int:
|
|
188
|
+
"""Execute an argument vector without parsing shell syntax.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
arguments: Program followed by literal arguments.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The child process exit status.
|
|
195
|
+
"""
|
|
196
|
+
if not arguments:
|
|
197
|
+
return 0
|
|
198
|
+
status = run_pipeline([Command(arguments=list(arguments))], jobs=self._state.jobs,
|
|
199
|
+
cwd=self._state.cwd, environment=self._state.environment,
|
|
200
|
+
verbose=self._state.verbose, pipefail=self._state.pipefail)
|
|
201
|
+
self._state.last_status = status
|
|
202
|
+
return status
|
|
203
|
+
|
|
204
|
+
def close(self) -> None:
|
|
205
|
+
"""Hang up jobs still managed by this session.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
None.
|
|
209
|
+
"""
|
|
210
|
+
self._state.jobs.shutdown()
|
|
211
|
+
|
|
212
|
+
def __enter__(self):
|
|
213
|
+
"""Enter a managed session context.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
This session.
|
|
217
|
+
"""
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
|
221
|
+
"""Close the session when leaving a managed context.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
exc_type: Active exception type, when present.
|
|
225
|
+
exc_value: Active exception value, when present.
|
|
226
|
+
traceback: Active exception traceback, when present.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
None.
|
|
230
|
+
"""
|
|
231
|
+
self.close()
|
|
232
|
+
|
|
233
|
+
def run_command(command_line: str, verbose: bool = False) -> int:
|
|
234
|
+
"""Execute one command line in a new pyesh automation session.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
command_line: Commands and supported pyesh operators to execute.
|
|
238
|
+
verbose: Whether to trace resolved execution.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
The final command status.
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
ValueError: If the command line has invalid syntax.
|
|
245
|
+
"""
|
|
246
|
+
return PyeshSession(verbose=verbose).run(command_line)
|