libPyshell 0.2.1__tar.gz → 0.4.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.1/libPyshell.egg-info → libPyshell-0.4.0}/PKG-INFO +9 -1
- {libPyshell-0.2.1 → libPyshell-0.4.0}/README.md +8 -0
- {libPyshell-0.2.1 → libPyshell-0.4.0/libPyshell.egg-info}/PKG-INFO +9 -1
- {libPyshell-0.2.1 → libPyshell-0.4.0}/setup.py +1 -1
- {libPyshell-0.2.1 → libPyshell-0.4.0}/src/__init__.py +98 -93
- {libPyshell-0.2.1 → libPyshell-0.4.0}/LICENSE +0 -0
- {libPyshell-0.2.1 → libPyshell-0.4.0}/MANIFEST.in +0 -0
- {libPyshell-0.2.1 → libPyshell-0.4.0}/libPyshell.egg-info/SOURCES.txt +0 -0
- {libPyshell-0.2.1 → libPyshell-0.4.0}/libPyshell.egg-info/dependency_links.txt +0 -0
- {libPyshell-0.2.1 → libPyshell-0.4.0}/libPyshell.egg-info/top_level.txt +0 -0
- {libPyshell-0.2.1 → libPyshell-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libPyshell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -37,6 +37,14 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
|
|
|
37
37
|
|
|
38
38
|
## Changelog
|
|
39
39
|
|
|
40
|
+
* 0.4.0 (2024-03-20)
|
|
41
|
+
* re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
|
|
42
|
+
disappear.
|
|
43
|
+
|
|
44
|
+
* 0.3.0 (2024-02-01)
|
|
45
|
+
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
46
|
+
and RunError which are slightly backwards incompatible.
|
|
47
|
+
|
|
40
48
|
* 0.2.0 (2024-01-29)
|
|
41
49
|
* Better static type information
|
|
42
50
|
|
|
@@ -26,6 +26,14 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
|
|
|
26
26
|
|
|
27
27
|
## Changelog
|
|
28
28
|
|
|
29
|
+
* 0.4.0 (2024-03-20)
|
|
30
|
+
* re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
|
|
31
|
+
disappear.
|
|
32
|
+
|
|
33
|
+
* 0.3.0 (2024-02-01)
|
|
34
|
+
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
35
|
+
and RunError which are slightly backwards incompatible.
|
|
36
|
+
|
|
29
37
|
* 0.2.0 (2024-01-29)
|
|
30
38
|
* Better static type information
|
|
31
39
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libPyshell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -37,6 +37,14 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
|
|
|
37
37
|
|
|
38
38
|
## Changelog
|
|
39
39
|
|
|
40
|
+
* 0.4.0 (2024-03-20)
|
|
41
|
+
* re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
|
|
42
|
+
disappear.
|
|
43
|
+
|
|
44
|
+
* 0.3.0 (2024-02-01)
|
|
45
|
+
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
46
|
+
and RunError which are slightly backwards incompatible.
|
|
47
|
+
|
|
40
48
|
* 0.2.0 (2024-01-29)
|
|
41
49
|
* Better static type information
|
|
42
50
|
|
|
@@ -50,7 +50,7 @@ except:
|
|
|
50
50
|
|
|
51
51
|
DEV_NULL = _devNull
|
|
52
52
|
|
|
53
|
-
_FILE = Union[int, IO[Any]
|
|
53
|
+
_FILE = Union[int, IO[Any]]
|
|
54
54
|
|
|
55
55
|
atexit.register(lambda: DEV_NULL.close())
|
|
56
56
|
|
|
@@ -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__
|
|
@@ -127,14 +128,19 @@ class RunError(ShellError):
|
|
|
127
128
|
"""
|
|
128
129
|
def __init__(self, cmd: Union[str, list[str]],
|
|
129
130
|
exitcode: int,
|
|
130
|
-
|
|
131
|
+
stdout: Union[str,bytes],
|
|
132
|
+
stderr: Union[str,bytes]):
|
|
131
133
|
self.cmd = cmd
|
|
132
134
|
self.exitcode = exitcode
|
|
133
135
|
self.stderr = stderr
|
|
136
|
+
self.stdout = stdout
|
|
134
137
|
msg = 'Command ' + repr(self.cmd) + " failed with exit code " + str(self.exitcode)
|
|
135
138
|
if stderr:
|
|
136
139
|
msg = msg + '\nstderr:\n' + str(stderr)
|
|
137
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)
|
|
138
144
|
|
|
139
145
|
def splitOn(splitter: str) -> Callable[[str], list[str]]:
|
|
140
146
|
"""Return a function that splits a string on the given splitter string.
|
|
@@ -173,19 +179,55 @@ def splitLines(s: str) -> list[str]:
|
|
|
173
179
|
else:
|
|
174
180
|
return s.split('\n')
|
|
175
181
|
|
|
182
|
+
def _decode(input: Union[str, bytes, None], encoding: str, errors: str) -> Optional[bytes]:
|
|
183
|
+
inputBytes: Optional[bytes] = None
|
|
184
|
+
if input and isinstance(input, str):
|
|
185
|
+
if encoding != 'raw':
|
|
186
|
+
inputBytes = input.encode(encoding, errors)
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError('Given str object as input, but encoding is raw')
|
|
189
|
+
elif input:
|
|
190
|
+
inputBytes = input
|
|
191
|
+
return inputBytes
|
|
192
|
+
|
|
193
|
+
CaptureType = Union[bool, Callable[[str], Any], _FILE, None]
|
|
194
|
+
|
|
195
|
+
def _handleCapture(capture: CaptureType) -> Optional[_FILE]:
|
|
196
|
+
if capture == True:
|
|
197
|
+
return subprocess.PIPE
|
|
198
|
+
elif callable(capture):
|
|
199
|
+
return subprocess.PIPE
|
|
200
|
+
elif capture is None:
|
|
201
|
+
return None
|
|
202
|
+
else:
|
|
203
|
+
return capture
|
|
204
|
+
|
|
205
|
+
def _massageOutput(data: Any, encoding: str, decodeErrors: Optional[str],
|
|
206
|
+
capture: CaptureType):
|
|
207
|
+
if not decodeErrors:
|
|
208
|
+
decodeErrors = 'strict'
|
|
209
|
+
if data and encoding != 'raw':
|
|
210
|
+
data = data.decode(encoding, errors=decodeErrors)
|
|
211
|
+
if not data:
|
|
212
|
+
data = ''
|
|
213
|
+
if isinstance(capture, Callable) and isinstance(data, str):
|
|
214
|
+
data = capture(data)
|
|
215
|
+
return data
|
|
216
|
+
|
|
176
217
|
def run(cmd: Union[list[str], str],
|
|
177
218
|
onError: Literal['raise', 'die', 'ignore']='raise',
|
|
178
219
|
input: Union[str, bytes, None]=None,
|
|
179
220
|
encoding: str='utf-8',
|
|
180
|
-
captureStdout: Union[bool,Callable[[str], Any],_FILE]=False,
|
|
181
|
-
captureStderr: Union[bool,Callable[[str], Any],_FILE]=False,
|
|
221
|
+
captureStdout: Union[bool, Callable[[str], Any], _FILE, None]=False,
|
|
222
|
+
captureStderr: Union[bool, Callable[[str], Any], _FILE, None]=False,
|
|
182
223
|
stderrToStdout: bool=False,
|
|
183
224
|
cwd: Optional[str]=None,
|
|
184
225
|
env: Optional[Dict[str, str]]=None,
|
|
185
226
|
freshEnv: Optional[Dict[str, str]]=None,
|
|
186
227
|
decodeErrors: str='replace',
|
|
187
228
|
decodeErrorsStdout: Optional[str]=None,
|
|
188
|
-
decodeErrorsStderr: Optional[str]=None
|
|
229
|
+
decodeErrorsStderr: Optional[str]=None,
|
|
230
|
+
encodeErrorsStdin: Optional[str]=None
|
|
189
231
|
) -> RunResult:
|
|
190
232
|
"""Runs the given command.
|
|
191
233
|
|
|
@@ -211,10 +253,11 @@ def run(cmd: Union[list[str], str],
|
|
|
211
253
|
* `stderrToStdout`: should stderr be sent to stdout?
|
|
212
254
|
* `cwd`: working directory
|
|
213
255
|
* `env`: dictionary with additional environment variables.
|
|
214
|
-
* `freshEnv`: dictionary with a completely fresh environment.
|
|
215
|
-
|
|
216
|
-
* `
|
|
217
|
-
|
|
256
|
+
* `freshEnv`: dictionary with a completely fresh environment. If `env` is also given, then
|
|
257
|
+
`freshEnv` is ignored.
|
|
258
|
+
* `decodeErrors`: how to handle decoding/encoding errors on stdout and stderr and stdin.
|
|
259
|
+
* `decodeErrorsStdout` and `decodeErrorsStderr` and `encodeErrorsStdin`: overwrite the value
|
|
260
|
+
of decodeErrors for stdout or stderr or stdin
|
|
218
261
|
|
|
219
262
|
Returns:
|
|
220
263
|
a `RunResult` value, given access to the captured stdout of the child process (if it was
|
|
@@ -222,105 +265,67 @@ def run(cmd: Union[list[str], str],
|
|
|
222
265
|
|
|
223
266
|
Raises: a `RunError` if `onError='raise'` and the command terminates with a non-zero exit code.
|
|
224
267
|
|
|
225
|
-
Starting with Python 3.5, the `subprocess` module defines a similar function.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
True
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
>>> run('/bin/echo foo', captureStdout=False)
|
|
235
|
-
|
|
236
|
-
>>> run('cat', captureStdout=True, input='blub')
|
|
237
|
-
|
|
268
|
+
Starting with Python 3.5, the `subprocess` module defines a similar function. This function
|
|
269
|
+
is just a wrapper for it.
|
|
270
|
+
|
|
271
|
+
>>> run('/bin/echo foo')
|
|
272
|
+
RunResult(exitcode=0, stdout='', stderr='')
|
|
273
|
+
>>> run('/bin/echo -n foo', captureStdout=True)
|
|
274
|
+
RunResult(exitcode=0, stdout='foo', stderr='')
|
|
275
|
+
>>> run('/bin/echo -n foo', captureStdout=lambda s: s + 'X')
|
|
276
|
+
RunResult(exitcode=0, stdout='fooX', stderr='')
|
|
277
|
+
>>> run('/bin/echo foo', captureStdout=False)
|
|
278
|
+
RunResult(exitcode=0, stdout='', stderr='')
|
|
279
|
+
>>> run('cat', captureStdout=True, input='blub')
|
|
280
|
+
RunResult(exitcode=0, stdout='blub', stderr='')
|
|
238
281
|
>>> try:
|
|
239
|
-
... run('false')
|
|
282
|
+
... run('/bin/echo -n foo 1>&2; /bin/echo -n bar; false', captureStdout=True, captureStderr=True)
|
|
240
283
|
... raise 'exception expected'
|
|
241
|
-
... except RunError:
|
|
242
|
-
...
|
|
284
|
+
... except RunError as e:
|
|
285
|
+
... print(repr(e))
|
|
243
286
|
...
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
decodeErrorsStderr = decodeErrors
|
|
256
|
-
stdoutIsFileLike = isinstance(captureStdout, int) or isinstance(captureStdout, IO)
|
|
257
|
-
stdoutIsProcFun = not stdoutIsFileLike and isinstance(captureStdout, Callable)
|
|
258
|
-
shouldReturnStdout = (stdoutIsProcFun or
|
|
259
|
-
(type(captureStdout) == bool and captureStdout))
|
|
260
|
-
stdout: _FILE = None
|
|
261
|
-
if shouldReturnStdout:
|
|
262
|
-
stdout = subprocess.PIPE
|
|
263
|
-
elif isinstance(captureStdout, int) or isinstance(captureStdout, IO):
|
|
264
|
-
stdout = captureStdout
|
|
265
|
-
stdin = None
|
|
266
|
-
if input:
|
|
267
|
-
stdin = subprocess.PIPE
|
|
268
|
-
stderr = None
|
|
287
|
+
RunError(cmd='/bin/echo -n foo 1>&2; /bin/echo -n bar; false', exitcode=1, stdout='bar', stderr='foo')
|
|
288
|
+
>>> run('false', onError='ignore')
|
|
289
|
+
RunResult(exitcode=1, stdout='', stderr='')
|
|
290
|
+
>>> run('/bin/echo -n foo; /bin/echo -n bar 1>&2', captureStdout=True, captureStderr=True)
|
|
291
|
+
RunResult(exitcode=0, stdout='foo', stderr='bar')
|
|
292
|
+
>>> run('/bin/echo -n foo 1>&2; /bin/echo -n bar', captureStderr=lambda s: s + 'X')
|
|
293
|
+
RunResult(exitcode=0, stdout='', stderr='fooX')
|
|
294
|
+
"""
|
|
295
|
+
shell = isinstance(cmd, str)
|
|
296
|
+
input = _decode(input, encoding, encodeErrorsStdin or decodeErrors)
|
|
297
|
+
stdout = _handleCapture(captureStdout)
|
|
269
298
|
if stderrToStdout:
|
|
270
299
|
stderr = subprocess.STDOUT
|
|
271
|
-
|
|
272
|
-
stderr =
|
|
273
|
-
|
|
274
|
-
inputBytes: Optional[bytes] = None
|
|
275
|
-
if input and isinstance(input, str):
|
|
276
|
-
input_str = '<' + str(len(input)) + ' characters>'
|
|
277
|
-
if encoding != 'raw':
|
|
278
|
-
inputBytes = input.encode(encoding)
|
|
279
|
-
else:
|
|
280
|
-
raise ValueError('Given str object as input, but encoding is raw')
|
|
281
|
-
elif input:
|
|
282
|
-
inputBytes = input
|
|
283
|
-
_debug('Running command ' + repr(cmd) + ' with captureStdout=' + str(captureStdout) +
|
|
284
|
-
', onError=' + onError + ', input=' + input_str)
|
|
285
|
-
popenEnv = None
|
|
300
|
+
else:
|
|
301
|
+
stderr = _handleCapture(captureStderr)
|
|
302
|
+
runEnv = None
|
|
286
303
|
if env:
|
|
287
|
-
|
|
288
|
-
|
|
304
|
+
runEnv = os.environ.copy()
|
|
305
|
+
runEnv.update(env)
|
|
289
306
|
elif freshEnv:
|
|
290
|
-
|
|
307
|
+
runEnv = freshEnv.copy()
|
|
291
308
|
if env:
|
|
292
|
-
|
|
309
|
+
runEnv.update(env)
|
|
293
310
|
# Ensure correct ordering of outputs
|
|
294
311
|
if stdout is None:
|
|
295
312
|
sys.stdout.flush()
|
|
296
313
|
if stderr is None:
|
|
297
314
|
sys.stderr.flush()
|
|
298
|
-
|
|
299
|
-
cmd, shell=
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if stderrData and encoding != 'raw':
|
|
307
|
-
stderrData = stderrData.decode(encoding, errors=decodeErrorsStderr)
|
|
308
|
-
exitcode = pipe.returncode
|
|
315
|
+
if _PYSHELL_DEBUG:
|
|
316
|
+
_debug(f'subprocess.run({cmd}, shell={shell}, input={input}, stdout={stdout}, ' \
|
|
317
|
+
f'stderr={stderr}, cwd={cwd}, env={runEnv})')
|
|
318
|
+
res = subprocess.run(cmd, shell=shell, input=input, stdout=stdout, stderr=stderr, cwd=cwd,
|
|
319
|
+
env=runEnv)
|
|
320
|
+
stdoutData = _massageOutput(res.stdout, encoding, decodeErrorsStdout or decodeErrors, captureStdout)
|
|
321
|
+
stderrData = _massageOutput(res.stderr, encoding, decodeErrorsStderr or decodeErrors, captureStderr)
|
|
322
|
+
exitcode = res.returncode
|
|
309
323
|
if onError == 'raise' and exitcode != 0:
|
|
310
|
-
|
|
311
|
-
if stderrToStdout:
|
|
312
|
-
d = stdoutData
|
|
313
|
-
err = RunError(cmd, exitcode, d)
|
|
324
|
+
err = RunError(cmd, exitcode, stdoutData, stderrData)
|
|
314
325
|
raise err
|
|
315
326
|
if onError == 'die' and exitcode != 0:
|
|
316
327
|
sys.exit(exitcode)
|
|
317
|
-
|
|
318
|
-
if not stdoutRes:
|
|
319
|
-
stdoutRes = ''
|
|
320
|
-
if not stdoutIsFileLike and isinstance(captureStdout, Callable) and \
|
|
321
|
-
isinstance(stdoutData, str):
|
|
322
|
-
stdoutRes = captureStdout(stdoutData)
|
|
323
|
-
return RunResult(stdoutRes, exitcode)
|
|
328
|
+
return RunResult(stdoutData, stderrData, exitcode)
|
|
324
329
|
|
|
325
330
|
# the quote function is stolen from https://hg.python.org/cpython/file/3.5/Lib/shlex.py
|
|
326
331
|
_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|