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 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ # __init__.py
2
+ __version__ = "1.0.0"
3
+
4
+ # Expose api for importing as package
5
+ from .api import PyeshSession, run_command
6
+ __all__ = ["PyeshSession", "__version__", "run_command"]
@@ -0,0 +1,8 @@
1
+ # __main__.py
2
+ # Entry point for the pyesh command-line application
3
+ # python -m pyesh
4
+
5
+ import sys
6
+ from .cli import main
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -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)