libPyshell 0.3.0__tar.gz → 0.4.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.
- {libPyshell-0.3.0/libPyshell.egg-info → libpyshell-0.4.1}/PKG-INFO +9 -1
- {libPyshell-0.3.0 → libpyshell-0.4.1}/README.md +8 -0
- {libPyshell-0.3.0 → libpyshell-0.4.1/libPyshell.egg-info}/PKG-INFO +9 -1
- {libPyshell-0.3.0 → libpyshell-0.4.1}/setup.py +1 -1
- {libPyshell-0.3.0 → libpyshell-0.4.1}/src/__init__.py +93 -81
- {libPyshell-0.3.0 → libpyshell-0.4.1}/LICENSE +0 -0
- {libPyshell-0.3.0 → libpyshell-0.4.1}/MANIFEST.in +0 -0
- {libPyshell-0.3.0 → libpyshell-0.4.1}/libPyshell.egg-info/SOURCES.txt +0 -0
- {libPyshell-0.3.0 → libpyshell-0.4.1}/libPyshell.egg-info/dependency_links.txt +0 -0
- {libPyshell-0.3.0 → libpyshell-0.4.1}/libPyshell.egg-info/top_level.txt +0 -0
- {libPyshell-0.3.0 → libpyshell-0.4.1}/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.1
|
|
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.1 (2024-09-12)
|
|
41
|
+
* fix capture handling
|
|
42
|
+
* add failOnError option to rm commands
|
|
43
|
+
|
|
44
|
+
* 0.4.0 (2024-03-20)
|
|
45
|
+
* re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
|
|
46
|
+
disappear.
|
|
47
|
+
|
|
40
48
|
* 0.3.0 (2024-02-01)
|
|
41
49
|
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
42
50
|
and RunError which are slightly backwards incompatible.
|
|
@@ -26,6 +26,14 @@ magicFiles = run(['grep', 'magic'] + files, captureStdout=splitLines, onError='i
|
|
|
26
26
|
|
|
27
27
|
## Changelog
|
|
28
28
|
|
|
29
|
+
* 0.4.1 (2024-09-12)
|
|
30
|
+
* fix capture handling
|
|
31
|
+
* add failOnError option to rm commands
|
|
32
|
+
|
|
33
|
+
* 0.4.0 (2024-03-20)
|
|
34
|
+
* re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
|
|
35
|
+
disappear.
|
|
36
|
+
|
|
29
37
|
* 0.3.0 (2024-02-01)
|
|
30
38
|
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
31
39
|
and RunError which are slightly backwards incompatible.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libPyshell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
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.1 (2024-09-12)
|
|
41
|
+
* fix capture handling
|
|
42
|
+
* add failOnError option to rm commands
|
|
43
|
+
|
|
44
|
+
* 0.4.0 (2024-03-20)
|
|
45
|
+
* re-implement run in terms of subprocess.run. This fixes a bug that caused stdout to
|
|
46
|
+
disappear.
|
|
47
|
+
|
|
40
48
|
* 0.3.0 (2024-02-01)
|
|
41
49
|
* uniform treatment when capturing stdout and stderr. This lead to changes to RunResult
|
|
42
50
|
and RunError which are slightly backwards incompatible.
|
|
@@ -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
|
|
|
@@ -179,19 +179,55 @@ def splitLines(s: str) -> list[str]:
|
|
|
179
179
|
else:
|
|
180
180
|
return s.split('\n')
|
|
181
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 == False:
|
|
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
|
+
|
|
182
217
|
def run(cmd: Union[list[str], str],
|
|
183
218
|
onError: Literal['raise', 'die', 'ignore']='raise',
|
|
184
219
|
input: Union[str, bytes, None]=None,
|
|
185
220
|
encoding: str='utf-8',
|
|
186
|
-
captureStdout: Union[bool,Callable[[str], Any],_FILE]=False,
|
|
187
|
-
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,
|
|
188
223
|
stderrToStdout: bool=False,
|
|
189
224
|
cwd: Optional[str]=None,
|
|
190
225
|
env: Optional[Dict[str, str]]=None,
|
|
191
226
|
freshEnv: Optional[Dict[str, str]]=None,
|
|
192
227
|
decodeErrors: str='replace',
|
|
193
228
|
decodeErrorsStdout: Optional[str]=None,
|
|
194
|
-
decodeErrorsStderr: Optional[str]=None
|
|
229
|
+
decodeErrorsStderr: Optional[str]=None,
|
|
230
|
+
encodeErrorsStdin: Optional[str]=None
|
|
195
231
|
) -> RunResult:
|
|
196
232
|
"""Runs the given command.
|
|
197
233
|
|
|
@@ -217,10 +253,11 @@ def run(cmd: Union[list[str], str],
|
|
|
217
253
|
* `stderrToStdout`: should stderr be sent to stdout?
|
|
218
254
|
* `cwd`: working directory
|
|
219
255
|
* `env`: dictionary with additional environment variables.
|
|
220
|
-
* `freshEnv`: dictionary with a completely fresh environment.
|
|
221
|
-
|
|
222
|
-
* `
|
|
223
|
-
|
|
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
|
|
224
261
|
|
|
225
262
|
Returns:
|
|
226
263
|
a `RunResult` value, given access to the captured stdout of the child process (if it was
|
|
@@ -228,7 +265,8 @@ def run(cmd: Union[list[str], str],
|
|
|
228
265
|
|
|
229
266
|
Raises: a `RunError` if `onError='raise'` and the command terminates with a non-zero exit code.
|
|
230
267
|
|
|
231
|
-
Starting with Python 3.5, the `subprocess` module defines a similar function.
|
|
268
|
+
Starting with Python 3.5, the `subprocess` module defines a similar function. This function
|
|
269
|
+
is just a wrapper for it.
|
|
232
270
|
|
|
233
271
|
>>> run('/bin/echo foo')
|
|
234
272
|
RunResult(exitcode=0, stdout='', stderr='')
|
|
@@ -254,64 +292,34 @@ def run(cmd: Union[list[str], str],
|
|
|
254
292
|
>>> run('/bin/echo -n foo 1>&2; /bin/echo -n bar', captureStderr=lambda s: s + 'X')
|
|
255
293
|
RunResult(exitcode=0, stdout='', stderr='fooX')
|
|
256
294
|
"""
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
cmd = cmd.replace('\x00', ' ')
|
|
261
|
-
cmd = cmd.replace('\n', ' ')
|
|
262
|
-
if decodeErrorsStdout is None:
|
|
263
|
-
decodeErrorsStdout = decodeErrors
|
|
264
|
-
if decodeErrorsStderr is None:
|
|
265
|
-
decodeErrorsStderr = decodeErrors
|
|
266
|
-
shouldReturnStdout = (isinstance(captureStdout, Callable) or
|
|
267
|
-
(type(captureStdout) == bool and captureStdout))
|
|
268
|
-
stdout: _FILE = None
|
|
269
|
-
if shouldReturnStdout:
|
|
270
|
-
stdout = subprocess.PIPE
|
|
271
|
-
elif isinstance(captureStdout, int) or isinstance(captureStdout, IO):
|
|
272
|
-
stdout = captureStdout
|
|
273
|
-
stdin = None
|
|
274
|
-
if input:
|
|
275
|
-
stdin = subprocess.PIPE
|
|
276
|
-
stderr = None
|
|
295
|
+
shell = isinstance(cmd, str)
|
|
296
|
+
input = _decode(input, encoding, encodeErrorsStdin or decodeErrors)
|
|
297
|
+
stdout = _handleCapture(captureStdout)
|
|
277
298
|
if stderrToStdout:
|
|
278
299
|
stderr = subprocess.STDOUT
|
|
279
|
-
|
|
280
|
-
stderr =
|
|
281
|
-
|
|
282
|
-
inputBytes: Optional[bytes] = None
|
|
283
|
-
if input and isinstance(input, str):
|
|
284
|
-
input_str = '<' + str(len(input)) + ' characters>'
|
|
285
|
-
if encoding != 'raw':
|
|
286
|
-
inputBytes = input.encode(encoding)
|
|
287
|
-
else:
|
|
288
|
-
raise ValueError('Given str object as input, but encoding is raw')
|
|
289
|
-
elif input:
|
|
290
|
-
inputBytes = input
|
|
291
|
-
_debug('Running command ' + repr(cmd) + ' with captureStdout=' + str(captureStdout) +
|
|
292
|
-
', onError=' + onError + ', input=' + input_str)
|
|
293
|
-
popenEnv = None
|
|
300
|
+
else:
|
|
301
|
+
stderr = _handleCapture(captureStderr)
|
|
302
|
+
runEnv = None
|
|
294
303
|
if env:
|
|
295
|
-
|
|
296
|
-
|
|
304
|
+
runEnv = os.environ.copy()
|
|
305
|
+
runEnv.update(env)
|
|
297
306
|
elif freshEnv:
|
|
298
|
-
|
|
307
|
+
runEnv = freshEnv.copy()
|
|
299
308
|
if env:
|
|
300
|
-
|
|
309
|
+
runEnv.update(env)
|
|
301
310
|
# Ensure correct ordering of outputs
|
|
302
311
|
if stdout is None:
|
|
303
312
|
sys.stdout.flush()
|
|
304
313
|
if stderr is None:
|
|
305
314
|
sys.stderr.flush()
|
|
306
|
-
|
|
307
|
-
cmd, shell=
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
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
|
|
315
323
|
if onError == 'raise' and exitcode != 0:
|
|
316
324
|
err = RunError(cmd, exitcode, stdoutData, stderrData)
|
|
317
325
|
raise err
|
|
@@ -319,16 +327,6 @@ def run(cmd: Union[list[str], str],
|
|
|
319
327
|
sys.exit(exitcode)
|
|
320
328
|
return RunResult(stdoutData, stderrData, exitcode)
|
|
321
329
|
|
|
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
|
|
331
|
-
|
|
332
330
|
# the quote function is stolen from https://hg.python.org/cpython/file/3.5/Lib/shlex.py
|
|
333
331
|
_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
|
334
332
|
def quote(s: str) -> str:
|
|
@@ -432,13 +430,15 @@ def removeFile(path: str):
|
|
|
432
430
|
|
|
433
431
|
def cp(src: str, target: str):
|
|
434
432
|
"""
|
|
435
|
-
Copy `src` to `target`.
|
|
433
|
+
Copy `src` to `target`. Behaves like the cp shell command.
|
|
436
434
|
|
|
437
435
|
* If `src` is a file and `target` is a file: overwrites `target`.
|
|
438
436
|
* If `src` is a file and `target` is a dirname: places the copy in directory `target`,
|
|
439
437
|
with the basename of `src.
|
|
440
|
-
* If `src` is a directory then `target` must also be a directory:
|
|
441
|
-
the `src` directory (*not* its content) to `target`.
|
|
438
|
+
* If `src` is a directory and `target` exists, then `target` must also be a directory:
|
|
439
|
+
copies the `src` directory (*not* its content) to `target`.
|
|
440
|
+
* If `src` is a directory and `target` does not exist: copies the `src` directory
|
|
441
|
+
and names the copy `target`.
|
|
442
442
|
"""
|
|
443
443
|
if isFile(src):
|
|
444
444
|
if isDir(target):
|
|
@@ -513,22 +513,34 @@ class workingDir:
|
|
|
513
513
|
cd(self.old_dir)
|
|
514
514
|
return False # reraise expection
|
|
515
515
|
|
|
516
|
-
def rm(path: str, force: bool=False):
|
|
516
|
+
def rm(path: str, force: bool=False, failOnError: bool=True):
|
|
517
517
|
"""
|
|
518
518
|
Remove the file at `path`.
|
|
519
519
|
"""
|
|
520
520
|
if force and not exists(path):
|
|
521
521
|
return
|
|
522
|
-
|
|
522
|
+
try:
|
|
523
|
+
os.remove(path)
|
|
524
|
+
except Exception as e:
|
|
525
|
+
if failOnError:
|
|
526
|
+
raise
|
|
527
|
+
else:
|
|
528
|
+
sys.stderr.write(str(e) + '\n')
|
|
523
529
|
|
|
524
|
-
def rmdir(d: str, recursive: bool=False):
|
|
530
|
+
def rmdir(d: str, recursive: bool=False, failOnError: bool=True):
|
|
525
531
|
"""
|
|
526
532
|
Remove directory `d`. Set `recursive=True` if the directory is not empty.
|
|
527
533
|
"""
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
534
|
+
try:
|
|
535
|
+
if recursive:
|
|
536
|
+
shutil.rmtree(d)
|
|
537
|
+
else:
|
|
538
|
+
os.rmdir(d)
|
|
539
|
+
except Exception as e:
|
|
540
|
+
if failOnError:
|
|
541
|
+
raise
|
|
542
|
+
else:
|
|
543
|
+
sys.stderr.write(str(e) + '\n')
|
|
532
544
|
|
|
533
545
|
# See https://stackoverflow.com/questions/9741351/how-to-find-exit-code-or-reason-when-atexit-callback-is-called-in-python
|
|
534
546
|
class _ExitHooks(object):
|
|
@@ -583,7 +595,7 @@ def _registerAtExit(action: Any, mode: AtExitMode):
|
|
|
583
595
|
def mkTempFile(suffix: str='', prefix: str='',
|
|
584
596
|
dir:Optional[str]=None,
|
|
585
597
|
deleteAtExit:AtExitMode=True):
|
|
586
|
-
"""Create a temporary file.
|
|
598
|
+
"""Create a temporary file name.
|
|
587
599
|
|
|
588
600
|
`deleteAtExit` controls if and how the file is deleted once the shell sript terminates.
|
|
589
601
|
It has one of the following values.
|
|
@@ -598,7 +610,7 @@ def mkTempFile(suffix: str='', prefix: str='',
|
|
|
598
610
|
if deleteAtExit:
|
|
599
611
|
def action():
|
|
600
612
|
if isFile(f):
|
|
601
|
-
rm(f)
|
|
613
|
+
rm(f, failOnError=False)
|
|
602
614
|
_registerAtExit(action, deleteAtExit)
|
|
603
615
|
return f
|
|
604
616
|
|
|
@@ -612,7 +624,7 @@ def mkTempDir(suffix: str='', prefix: str='tmp',
|
|
|
612
624
|
if deleteAtExit:
|
|
613
625
|
def action():
|
|
614
626
|
if isDir(d):
|
|
615
|
-
rmdir(d, True)
|
|
627
|
+
rmdir(d, recursive=True, failOnError=False)
|
|
616
628
|
_registerAtExit(action, deleteAtExit)
|
|
617
629
|
return d
|
|
618
630
|
|
|
@@ -648,7 +660,7 @@ class tempDir:
|
|
|
648
660
|
return False # reraise
|
|
649
661
|
if self.delete:
|
|
650
662
|
if isDir(self.dir_to_delete):
|
|
651
|
-
rmdir(self.dir_to_delete, recursive=True)
|
|
663
|
+
rmdir(self.dir_to_delete, recursive=True, failOnError=False)
|
|
652
664
|
return False # reraise expection
|
|
653
665
|
|
|
654
666
|
def ls(d: str, *globs: str) -> list[str]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|