libPyshell 0.2.0__tar.gz → 0.3.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.
- {libPyshell-0.2.0/libPyshell.egg-info → libPyshell-0.3.0}/PKG-INFO +6 -2
- {libPyshell-0.2.0 → libPyshell-0.3.0}/README.md +4 -0
- {libPyshell-0.2.0 → libPyshell-0.3.0/libPyshell.egg-info}/PKG-INFO +6 -2
- {libPyshell-0.2.0 → libPyshell-0.3.0}/setup.py +2 -2
- {libPyshell-0.2.0 → libPyshell-0.3.0}/src/__init__.py +63 -48
- {libPyshell-0.2.0 → libPyshell-0.3.0}/LICENSE +0 -0
- {libPyshell-0.2.0 → libPyshell-0.3.0}/MANIFEST.in +0 -0
- {libPyshell-0.2.0 → libPyshell-0.3.0}/libPyshell.egg-info/SOURCES.txt +0 -0
- {libPyshell-0.2.0 → libPyshell-0.3.0}/libPyshell.egg-info/dependency_links.txt +0 -0
- {libPyshell-0.2.0 → libPyshell-0.3.0}/libPyshell.egg-info/top_level.txt +0 -0
- {libPyshell-0.2.0 → libPyshell-0.3.0}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libPyshell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Support for writing shell scripts in Python
|
|
5
5
|
Home-page: https://github.com/skogsbaer/libPyshell
|
|
6
6
|
Author: Stefan Wehr
|
|
7
7
|
Author-email: stefan.wehr@gmail.com
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
|
|
@@ -37,6 +37,10 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
|
|
|
37
37
|
|
|
38
38
|
## Changelog
|
|
39
39
|
|
|
40
|
+
* 0.3.0 (2024-02-01)
|
|
41
|
+
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
42
|
+
and RunError which are slightly backwards incompatible.
|
|
43
|
+
|
|
40
44
|
* 0.2.0 (2024-01-29)
|
|
41
45
|
* Better static type information
|
|
42
46
|
|
|
@@ -26,6 +26,10 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
|
|
|
26
26
|
|
|
27
27
|
## Changelog
|
|
28
28
|
|
|
29
|
+
* 0.3.0 (2024-02-01)
|
|
30
|
+
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
31
|
+
and RunError which are slightly backwards incompatible.
|
|
32
|
+
|
|
29
33
|
* 0.2.0 (2024-01-29)
|
|
30
34
|
* Better static type information
|
|
31
35
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libPyshell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Support for writing shell scripts in Python
|
|
5
5
|
Home-page: https://github.com/skogsbaer/libPyshell
|
|
6
6
|
Author: Stefan Wehr
|
|
7
7
|
Author-email: stefan.wehr@gmail.com
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
|
|
@@ -37,6 +37,10 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
|
|
|
37
37
|
|
|
38
38
|
## Changelog
|
|
39
39
|
|
|
40
|
+
* 0.3.0 (2024-02-01)
|
|
41
|
+
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
42
|
+
and RunError which are slightly backwards incompatible.
|
|
43
|
+
|
|
40
44
|
* 0.2.0 (2024-01-29)
|
|
41
45
|
* Better static type information
|
|
42
46
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from distutils.core import setup
|
|
4
4
|
|
|
5
|
-
VERSION = '0.
|
|
5
|
+
VERSION = '0.3.0'
|
|
6
6
|
|
|
7
7
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
8
8
|
long_description = fh.read()
|
|
@@ -17,5 +17,5 @@ setup(name='libPyshell',
|
|
|
17
17
|
url='https://github.com/skogsbaer/libPyshell',
|
|
18
18
|
package_dir={'shell': 'src'},
|
|
19
19
|
packages=['shell'],
|
|
20
|
-
python_requires='>=3.
|
|
20
|
+
python_requires='>=3.9'
|
|
21
21
|
)
|
|
@@ -50,7 +50,7 @@ except:
|
|
|
50
50
|
|
|
51
51
|
DEV_NULL = _devNull
|
|
52
52
|
|
|
53
|
-
_FILE
|
|
53
|
+
_FILE = Union[int, IO[Any], None]
|
|
54
54
|
|
|
55
55
|
atexit.register(lambda: DEV_NULL.close())
|
|
56
56
|
|
|
@@ -62,7 +62,7 @@ def fatal(s: str):
|
|
|
62
62
|
"""Display an error message to stderr."""
|
|
63
63
|
sys.stderr.write('ERROR: ' + str(s) + '\n')
|
|
64
64
|
|
|
65
|
-
def resolveProg(*l: str) -> str
|
|
65
|
+
def resolveProg(*l: str) -> Optional[str]:
|
|
66
66
|
"""Return the first program in the list that exist and is runnable.
|
|
67
67
|
>>> resolveProg()
|
|
68
68
|
>>> resolveProg('foobarbaz', 'cat', 'grep')
|
|
@@ -94,11 +94,12 @@ class RunResult:
|
|
|
94
94
|
attribute `stdout` contains the output printed in stdout (only if `run`
|
|
95
95
|
was invoked with `captureStdout=True`).
|
|
96
96
|
"""
|
|
97
|
-
def __init__(self, stdout: Any, exitcode: int):
|
|
97
|
+
def __init__(self, stdout: Any, stderr: Any, exitcode: int):
|
|
98
98
|
self.stdout = stdout
|
|
99
|
+
self.stderr = stderr
|
|
99
100
|
self.exitcode = exitcode
|
|
100
101
|
def __repr__(self):
|
|
101
|
-
return 'RunResult(exitcode=%d, stdout=%r)
|
|
102
|
+
return 'RunResult(exitcode=%d, stdout=%r, stderr=%r)' % (self.exitcode, self.stdout, self.stderr)
|
|
102
103
|
def __eq__(self, other: Any):
|
|
103
104
|
if type(other) is type(self):
|
|
104
105
|
return self.__dict__ == other.__dict__
|
|
@@ -125,14 +126,21 @@ class RunError(ShellError):
|
|
|
125
126
|
* `exitcode`
|
|
126
127
|
* `stderr`: output on stderr (if `run` configured to capture this output)
|
|
127
128
|
"""
|
|
128
|
-
def __init__(self, cmd: str
|
|
129
|
+
def __init__(self, cmd: Union[str, list[str]],
|
|
130
|
+
exitcode: int,
|
|
131
|
+
stdout: Union[str,bytes],
|
|
132
|
+
stderr: Union[str,bytes]):
|
|
129
133
|
self.cmd = cmd
|
|
130
134
|
self.exitcode = exitcode
|
|
131
135
|
self.stderr = stderr
|
|
136
|
+
self.stdout = stdout
|
|
132
137
|
msg = 'Command ' + repr(self.cmd) + " failed with exit code " + str(self.exitcode)
|
|
133
138
|
if stderr:
|
|
134
139
|
msg = msg + '\nstderr:\n' + str(stderr)
|
|
135
140
|
super(RunError, self).__init__(msg)
|
|
141
|
+
def __repr__(self):
|
|
142
|
+
return 'RunError(cmd=%r, exitcode=%d, stdout=%r, stderr=%r)' % \
|
|
143
|
+
(self.cmd, self.exitcode, self.stdout, self.stderr)
|
|
136
144
|
|
|
137
145
|
def splitOn(splitter: str) -> Callable[[str], list[str]]:
|
|
138
146
|
"""Return a function that splits a string on the given splitter string.
|
|
@@ -173,10 +181,10 @@ def splitLines(s: str) -> list[str]:
|
|
|
173
181
|
|
|
174
182
|
def run(cmd: Union[list[str], str],
|
|
175
183
|
onError: Literal['raise', 'die', 'ignore']='raise',
|
|
176
|
-
input: str
|
|
184
|
+
input: Union[str, bytes, None]=None,
|
|
177
185
|
encoding: str='utf-8',
|
|
178
|
-
captureStdout: bool
|
|
179
|
-
captureStderr: bool
|
|
186
|
+
captureStdout: Union[bool,Callable[[str], Any],_FILE]=False,
|
|
187
|
+
captureStderr: Union[bool,Callable[[str], Any],_FILE]=False,
|
|
180
188
|
stderrToStdout: bool=False,
|
|
181
189
|
cwd: Optional[str]=None,
|
|
182
190
|
env: Optional[Dict[str, str]]=None,
|
|
@@ -203,7 +211,8 @@ def run(cmd: Union[list[str], str],
|
|
|
203
211
|
* False: stdout is not captured and goes to stdout of the parent process (the default)
|
|
204
212
|
* True: stdout is captured and returned
|
|
205
213
|
* A function: stdout is captured and the result of applying the function to the captured
|
|
206
|
-
output is returned.
|
|
214
|
+
output is returned. In this case, encoding must not be `'raw'`.
|
|
215
|
+
Use splitLines as this function to split the output into lines
|
|
207
216
|
* An existing file descriptor or a file object: stdout goes to the file descriptor or file
|
|
208
217
|
* `stderrToStdout`: should stderr be sent to stdout?
|
|
209
218
|
* `cwd`: working directory
|
|
@@ -221,25 +230,29 @@ def run(cmd: Union[list[str], str],
|
|
|
221
230
|
|
|
222
231
|
Starting with Python 3.5, the `subprocess` module defines a similar function.
|
|
223
232
|
|
|
224
|
-
>>> run('/bin/echo foo')
|
|
225
|
-
|
|
226
|
-
>>> run('/bin/echo -n foo', captureStdout=True)
|
|
227
|
-
|
|
228
|
-
>>> run('/bin/echo -n foo', captureStdout=lambda s: s + 'X')
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
True
|
|
233
|
-
|
|
234
|
-
True
|
|
233
|
+
>>> run('/bin/echo foo')
|
|
234
|
+
RunResult(exitcode=0, stdout='', stderr='')
|
|
235
|
+
>>> run('/bin/echo -n foo', captureStdout=True)
|
|
236
|
+
RunResult(exitcode=0, stdout='foo', stderr='')
|
|
237
|
+
>>> run('/bin/echo -n foo', captureStdout=lambda s: s + 'X')
|
|
238
|
+
RunResult(exitcode=0, stdout='fooX', stderr='')
|
|
239
|
+
>>> run('/bin/echo foo', captureStdout=False)
|
|
240
|
+
RunResult(exitcode=0, stdout='', stderr='')
|
|
241
|
+
>>> run('cat', captureStdout=True, input='blub')
|
|
242
|
+
RunResult(exitcode=0, stdout='blub', stderr='')
|
|
235
243
|
>>> try:
|
|
236
|
-
... run('false')
|
|
244
|
+
... run('/bin/echo -n foo 1>&2; /bin/echo -n bar; false', captureStdout=True, captureStderr=True)
|
|
237
245
|
... raise 'exception expected'
|
|
238
|
-
... except RunError:
|
|
239
|
-
...
|
|
246
|
+
... except RunError as e:
|
|
247
|
+
... print(repr(e))
|
|
240
248
|
...
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
RunError(cmd='/bin/echo -n foo 1>&2; /bin/echo -n bar; false', exitcode=1, stdout='bar', stderr='foo')
|
|
250
|
+
>>> run('false', onError='ignore')
|
|
251
|
+
RunResult(exitcode=1, stdout='', stderr='')
|
|
252
|
+
>>> run('/bin/echo -n foo; /bin/echo -n bar 1>&2', captureStdout=True, captureStderr=True)
|
|
253
|
+
RunResult(exitcode=0, stdout='foo', stderr='bar')
|
|
254
|
+
>>> run('/bin/echo -n foo 1>&2; /bin/echo -n bar', captureStderr=lambda s: s + 'X')
|
|
255
|
+
RunResult(exitcode=0, stdout='', stderr='fooX')
|
|
243
256
|
"""
|
|
244
257
|
if type(cmd) != str and type(cmd) != list:
|
|
245
258
|
raise ShellError('cmd parameter must be a string or a list')
|
|
@@ -250,9 +263,7 @@ def run(cmd: Union[list[str], str],
|
|
|
250
263
|
decodeErrorsStdout = decodeErrors
|
|
251
264
|
if decodeErrorsStderr is None:
|
|
252
265
|
decodeErrorsStderr = decodeErrors
|
|
253
|
-
|
|
254
|
-
stdoutIsProcFun = not stdoutIsFileLike and isinstance(captureStdout, Callable)
|
|
255
|
-
shouldReturnStdout = (stdoutIsProcFun or
|
|
266
|
+
shouldReturnStdout = (isinstance(captureStdout, Callable) or
|
|
256
267
|
(type(captureStdout) == bool and captureStdout))
|
|
257
268
|
stdout: _FILE = None
|
|
258
269
|
if shouldReturnStdout:
|
|
@@ -268,7 +279,7 @@ def run(cmd: Union[list[str], str],
|
|
|
268
279
|
elif captureStderr:
|
|
269
280
|
stderr = subprocess.PIPE
|
|
270
281
|
input_str = 'None'
|
|
271
|
-
inputBytes: bytes
|
|
282
|
+
inputBytes: Optional[bytes] = None
|
|
272
283
|
if input and isinstance(input, str):
|
|
273
284
|
input_str = '<' + str(len(input)) + ' characters>'
|
|
274
285
|
if encoding != 'raw':
|
|
@@ -298,25 +309,25 @@ def run(cmd: Union[list[str], str],
|
|
|
298
309
|
cwd=cwd, env=popenEnv
|
|
299
310
|
)
|
|
300
311
|
(stdoutData, stderrData) = pipe.communicate(input=inputBytes)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if stderrData and encoding != 'raw':
|
|
304
|
-
stderrData = stderrData.decode(encoding, errors=decodeErrorsStderr)
|
|
312
|
+
stdoutData = massageOutput(stdoutData, encoding, decodeErrorsStdout, captureStdout)
|
|
313
|
+
stderrData = massageOutput(stderrData, encoding, decodeErrorsStderr, captureStderr)
|
|
305
314
|
exitcode = pipe.returncode
|
|
306
315
|
if onError == 'raise' and exitcode != 0:
|
|
307
|
-
|
|
308
|
-
if stderrToStdout:
|
|
309
|
-
d = stdoutData
|
|
310
|
-
err = RunError(cmd, exitcode, d)
|
|
316
|
+
err = RunError(cmd, exitcode, stdoutData, stderrData)
|
|
311
317
|
raise err
|
|
312
318
|
if onError == 'die' and exitcode != 0:
|
|
313
319
|
sys.exit(exitcode)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
+
return RunResult(stdoutData, stderrData, exitcode)
|
|
321
|
+
|
|
322
|
+
def massageOutput(data: Any, encoding: str, decodeErrors: str,
|
|
323
|
+
capture: Union[bool,Callable[[str], Any],_FILE]):
|
|
324
|
+
if data and encoding != 'raw':
|
|
325
|
+
data = data.decode(encoding, errors=decodeErrors)
|
|
326
|
+
if not data:
|
|
327
|
+
data = ''
|
|
328
|
+
if isinstance(capture, Callable) and isinstance(data, str):
|
|
329
|
+
data = capture(data)
|
|
330
|
+
return data
|
|
320
331
|
|
|
321
332
|
# the quote function is stolen from https://hg.python.org/cpython/file/3.5/Lib/shlex.py
|
|
322
333
|
_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
|
@@ -531,7 +542,7 @@ class _ExitHooks(object):
|
|
|
531
542
|
sys.exit = self.exit
|
|
532
543
|
sys.excepthook = self.exc_handler
|
|
533
544
|
|
|
534
|
-
def exit(self, code: int
|
|
545
|
+
def exit(self, code: Optional[int]=0):
|
|
535
546
|
if code is None:
|
|
536
547
|
myCode = 0
|
|
537
548
|
elif type(code) != int:
|
|
@@ -569,7 +580,9 @@ def _registerAtExit(action: Any, mode: AtExitMode):
|
|
|
569
580
|
_debug('Not running exit action')
|
|
570
581
|
atexit.register(f)
|
|
571
582
|
|
|
572
|
-
def mkTempFile(suffix: str='', prefix: str='',
|
|
583
|
+
def mkTempFile(suffix: str='', prefix: str='',
|
|
584
|
+
dir:Optional[str]=None,
|
|
585
|
+
deleteAtExit:AtExitMode=True):
|
|
573
586
|
"""Create a temporary file.
|
|
574
587
|
|
|
575
588
|
`deleteAtExit` controls if and how the file is deleted once the shell sript terminates.
|
|
@@ -589,7 +602,9 @@ def mkTempFile(suffix: str='', prefix: str='', dir:str|None=None, deleteAtExit:A
|
|
|
589
602
|
_registerAtExit(action, deleteAtExit)
|
|
590
603
|
return f
|
|
591
604
|
|
|
592
|
-
def mkTempDir(suffix: str='', prefix: str='tmp',
|
|
605
|
+
def mkTempDir(suffix: str='', prefix: str='tmp',
|
|
606
|
+
dir: Optional[str]=None,
|
|
607
|
+
deleteAtExit: AtExitMode=True):
|
|
593
608
|
"""Create a temporary directory. The `deleteAtExit` parameter
|
|
594
609
|
has the same meaning as for `mkTempFile`.
|
|
595
610
|
"""
|
|
@@ -615,7 +630,7 @@ class tempDir:
|
|
|
615
630
|
With `delete=False`, deletion is deactivated. With `onException=False`, deletion
|
|
616
631
|
is only performed if the `with`-block finishes without an exception.
|
|
617
632
|
"""
|
|
618
|
-
def __init__(self, suffix: str='', prefix: str='tmp', dir: str
|
|
633
|
+
def __init__(self, suffix: str='', prefix: str='tmp', dir: Optional[str]=None,
|
|
619
634
|
onException: bool=True, delete: bool=True):
|
|
620
635
|
self.suffix = suffix
|
|
621
636
|
self.prefix = prefix
|
|
@@ -644,7 +659,7 @@ def ls(d: str, *globs: str) -> list[str]:
|
|
|
644
659
|
|
|
645
660
|
The pathnames in the result list contain the directory part `d`.
|
|
646
661
|
|
|
647
|
-
>>> '../src/
|
|
662
|
+
>>> '../src/__init__.py' in ls('../src/', '*.py', '*.txt')
|
|
648
663
|
True
|
|
649
664
|
"""
|
|
650
665
|
res: list[str] = []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|