skilleter-thingy 0.2.13__py3-none-any.whl → 0.2.14__py3-none-any.whl
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.
Potentially problematic release.
This version of skilleter-thingy might be problematic. Click here for more details.
- skilleter_thingy/ffind.py +23 -20
- skilleter_thingy/git_cleanup.py +1 -1
- skilleter_thingy/git_review.py +1 -2
- skilleter_thingy/git_update.py +3 -0
- skilleter_thingy/gitcmp_helper.py +17 -14
- skilleter_thingy/rpylint.py +28 -20
- skilleter_thingy/tfm.py +0 -2
- skilleter_thingy/thingy/docker.py +11 -11
- skilleter_thingy/thingy/files.py +6 -6
- skilleter_thingy/thingy/git.py +2 -2
- skilleter_thingy/thingy/popup.py +2 -1
- skilleter_thingy/thingy/run.py +111 -181
- skilleter_thingy/x.py +3 -0
- {skilleter_thingy-0.2.13.dist-info → skilleter_thingy-0.2.14.dist-info}/METADATA +1 -1
- {skilleter_thingy-0.2.13.dist-info → skilleter_thingy-0.2.14.dist-info}/RECORD +19 -19
- skilleter_thingy/thingy/process.py +0 -32
- {skilleter_thingy-0.2.13.dist-info → skilleter_thingy-0.2.14.dist-info}/WHEEL +0 -0
- {skilleter_thingy-0.2.13.dist-info → skilleter_thingy-0.2.14.dist-info}/entry_points.txt +0 -0
- {skilleter_thingy-0.2.13.dist-info → skilleter_thingy-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {skilleter_thingy-0.2.13.dist-info → skilleter_thingy-0.2.14.dist-info}/top_level.txt +0 -0
skilleter_thingy/ffind.py
CHANGED
|
@@ -26,11 +26,10 @@ import pwd
|
|
|
26
26
|
import datetime
|
|
27
27
|
import re
|
|
28
28
|
import shlex
|
|
29
|
-
import copy
|
|
30
29
|
import logging
|
|
30
|
+
import subprocess
|
|
31
31
|
|
|
32
32
|
from thingy import git
|
|
33
|
-
from thingy import run
|
|
34
33
|
from thingy import dircolors
|
|
35
34
|
from thingy import colour
|
|
36
35
|
|
|
@@ -39,7 +38,7 @@ from thingy import colour
|
|
|
39
38
|
def error(msg, status=1):
|
|
40
39
|
""" Report an error message and exit """
|
|
41
40
|
|
|
42
|
-
sys.stderr.write('
|
|
41
|
+
sys.stderr.write(f'{msg}\n')
|
|
43
42
|
sys.exit(status)
|
|
44
43
|
|
|
45
44
|
################################################################################
|
|
@@ -48,7 +47,7 @@ def report_exception(exc):
|
|
|
48
47
|
""" Handle an exception triggered inside os.walk - currently just reports
|
|
49
48
|
the exception - typically a permission error. """
|
|
50
49
|
|
|
51
|
-
sys.stderr.write('
|
|
50
|
+
sys.stderr.write(f'{exc}\n')
|
|
52
51
|
|
|
53
52
|
################################################################################
|
|
54
53
|
|
|
@@ -68,17 +67,18 @@ def report_file(args, filepath, filestat, dircolour):
|
|
|
68
67
|
if args.exec:
|
|
69
68
|
# Copy the exec string and insert the file
|
|
70
69
|
|
|
71
|
-
cmd =
|
|
72
|
-
|
|
70
|
+
cmd = []
|
|
71
|
+
for i in args.exec:
|
|
72
|
+
cmd.append(i.replace('^', filepath))
|
|
73
73
|
|
|
74
74
|
logging.debug('Running "%s"', ' '.join(cmd))
|
|
75
75
|
|
|
76
76
|
# Attempt to run the command
|
|
77
77
|
|
|
78
78
|
try:
|
|
79
|
-
|
|
80
|
-
except
|
|
81
|
-
error(exc.
|
|
79
|
+
subprocess.run(cmd, check=True)
|
|
80
|
+
except subprocess.CalledProcessError as exc:
|
|
81
|
+
error('%s failed with status=%d' % (' '.join(cmd), exc.returncode))
|
|
82
82
|
except FileNotFoundError:
|
|
83
83
|
error('File not found attempting to run "%s"' % ' '.join(cmd))
|
|
84
84
|
except PermissionError:
|
|
@@ -86,7 +86,7 @@ def report_file(args, filepath, filestat, dircolour):
|
|
|
86
86
|
|
|
87
87
|
if args.verbose or not args.exec:
|
|
88
88
|
if args.zero:
|
|
89
|
-
sys.stdout.write('
|
|
89
|
+
sys.stdout.write(f'{filepath}\0')
|
|
90
90
|
else:
|
|
91
91
|
# Colourise output if required
|
|
92
92
|
|
|
@@ -96,7 +96,7 @@ def report_file(args, filepath, filestat, dircolour):
|
|
|
96
96
|
# Quote the file if necessary
|
|
97
97
|
|
|
98
98
|
if not args.unquoted and ' ' in filepath:
|
|
99
|
-
filepath = '"
|
|
99
|
+
filepath = f'"{filepath}"'
|
|
100
100
|
|
|
101
101
|
# Output full details or just the filename
|
|
102
102
|
|
|
@@ -186,7 +186,7 @@ def grep_match(regex, filename):
|
|
|
186
186
|
|
|
187
187
|
recomp = re.compile(regex)
|
|
188
188
|
|
|
189
|
-
with open(filename, 'r', errors='ignore') as infile:
|
|
189
|
+
with open(filename, 'r', errors='ignore', encoding='utf8') as infile:
|
|
190
190
|
for text in infile:
|
|
191
191
|
if recomp.search(text):
|
|
192
192
|
break
|
|
@@ -299,7 +299,7 @@ def validate_arguments(args):
|
|
|
299
299
|
if args.type:
|
|
300
300
|
for t in args.type:
|
|
301
301
|
if t not in ['b', 'c', 'd', 'p', 'f', 'l', 's']:
|
|
302
|
-
error('Invalid type "
|
|
302
|
+
error(f'Invalid type "{t}"')
|
|
303
303
|
|
|
304
304
|
# Precompile regexes if using them
|
|
305
305
|
|
|
@@ -392,14 +392,15 @@ def validate_arguments(args):
|
|
|
392
392
|
# marker if it isn't there.
|
|
393
393
|
|
|
394
394
|
if args.exec:
|
|
395
|
-
|
|
395
|
+
replacements = args.exec.count('^')
|
|
396
|
+
|
|
397
|
+
if replacements > 1:
|
|
396
398
|
error('Too many "^" characters in the exec string')
|
|
399
|
+
elif not replacements:
|
|
400
|
+
args.exec = f'{args.exec} ^'
|
|
397
401
|
|
|
398
402
|
args.exec = shlex.split(args.exec)
|
|
399
403
|
|
|
400
|
-
if '^' not in args.exec:
|
|
401
|
-
args.exec.append('^')
|
|
402
|
-
|
|
403
404
|
# If the path option has been used, try to switch to the specified directory
|
|
404
405
|
|
|
405
406
|
if args.path:
|
|
@@ -501,9 +502,11 @@ def main():
|
|
|
501
502
|
diff_cmd.append(file)
|
|
502
503
|
|
|
503
504
|
try:
|
|
504
|
-
|
|
505
|
-
except
|
|
506
|
-
error(exc)
|
|
505
|
+
subprocess.run(diff_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
506
|
+
except subprocess.CalledProcessError as exc:
|
|
507
|
+
error(f'Diff failed with status={exc.returncode}')
|
|
508
|
+
except FileNotFoundError:
|
|
509
|
+
error('Unable to run %s' % ' '.join(diff_cmd))
|
|
507
510
|
|
|
508
511
|
# Report the number of objects found
|
|
509
512
|
|
skilleter_thingy/git_cleanup.py
CHANGED
skilleter_thingy/git_review.py
CHANGED
|
@@ -47,7 +47,6 @@ import sys
|
|
|
47
47
|
import argparse
|
|
48
48
|
import curses
|
|
49
49
|
import curses.panel
|
|
50
|
-
import curses.textpad
|
|
51
50
|
import pickle
|
|
52
51
|
import fnmatch
|
|
53
52
|
import subprocess
|
|
@@ -200,7 +199,7 @@ class PopUp():
|
|
|
200
199
|
else:
|
|
201
200
|
break
|
|
202
201
|
|
|
203
|
-
def __exit__(self,
|
|
202
|
+
def __exit__(self, _exc_type, _exc_value, _exc_traceback):
|
|
204
203
|
""" Remove the popup """
|
|
205
204
|
|
|
206
205
|
if self.panel:
|
skilleter_thingy/git_update.py
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
TODO: Avoid lots of pulls - should be able to fetch then updated each local branch.
|
|
20
20
|
TODO: Add option to specify name of master branch or additional names to add to the list
|
|
21
|
+
TODO: Config entry for regularly-ignored branches
|
|
22
|
+
TODO: Config entry for branches that shouldn't be rebased (main, master, release/*)
|
|
23
|
+
TODO: Command line option when using -a to skip working trees that are modified
|
|
21
24
|
"""
|
|
22
25
|
################################################################################
|
|
23
26
|
|
|
@@ -43,9 +43,9 @@ import argparse
|
|
|
43
43
|
import filecmp
|
|
44
44
|
import re
|
|
45
45
|
import logging
|
|
46
|
+
import subprocess
|
|
46
47
|
|
|
47
48
|
from thingy import colour
|
|
48
|
-
from thingy import run
|
|
49
49
|
from thingy import files
|
|
50
50
|
from thingy import git
|
|
51
51
|
from thingy import dircolors
|
|
@@ -81,7 +81,7 @@ def report_permissions(perm):
|
|
|
81
81
|
permtext.append(mask_char if permissions & mask else '-')
|
|
82
82
|
mask >>= 1
|
|
83
83
|
|
|
84
|
-
return '
|
|
84
|
+
return ''.join(permtext)
|
|
85
85
|
|
|
86
86
|
################################################################################
|
|
87
87
|
|
|
@@ -146,7 +146,7 @@ def main():
|
|
|
146
146
|
# Check and handle for the simple case of an unmerged file
|
|
147
147
|
|
|
148
148
|
if args.old_file is None:
|
|
149
|
-
colour.write('[CYAN
|
|
149
|
+
colour.write(f'[CYAN:{args.file_path}] is not merged')
|
|
150
150
|
sys.exit(0)
|
|
151
151
|
|
|
152
152
|
# Make sure that we have all the expected parameters
|
|
@@ -158,11 +158,11 @@ def main():
|
|
|
158
158
|
# Make sure we can access the temporary files supplied
|
|
159
159
|
|
|
160
160
|
if not os.access(args.old_file, os.R_OK):
|
|
161
|
-
sys.stderr.write('Unable to read temporary old file:
|
|
161
|
+
sys.stderr.write(f'Unable to read temporary old file: {args.old_file}\n')
|
|
162
162
|
sys.exit(2)
|
|
163
163
|
|
|
164
164
|
if not os.access(args.new_file, os.R_OK):
|
|
165
|
-
sys.stderr.write('Unable to read temporary new file:
|
|
165
|
+
sys.stderr.write(f'Unable to read temporary new file: {args.new_file}\n')
|
|
166
166
|
sys.exit(2)
|
|
167
167
|
|
|
168
168
|
dc = dircolors.Dircolors()
|
|
@@ -195,12 +195,12 @@ def main():
|
|
|
195
195
|
# If processing more than one file, append he index and total number of files
|
|
196
196
|
|
|
197
197
|
if path_total > 0:
|
|
198
|
-
heading.append('(
|
|
198
|
+
heading.append(f'({path_count}/{path_total})')
|
|
199
199
|
|
|
200
200
|
# Check for newly created/deleted files (other version will be '/dev/null')
|
|
201
201
|
|
|
202
|
-
created_file =
|
|
203
|
-
deleted_file =
|
|
202
|
+
created_file = args.old_file == '/dev/null'
|
|
203
|
+
deleted_file = args.new_file == '/dev/null'
|
|
204
204
|
|
|
205
205
|
if created_file:
|
|
206
206
|
heading.append('(new file)')
|
|
@@ -232,11 +232,11 @@ def main():
|
|
|
232
232
|
formatted_new_size = files.format_size(new_size, always_suffix=True)
|
|
233
233
|
|
|
234
234
|
if created_file:
|
|
235
|
-
colour.write(' New size: [CYAN
|
|
235
|
+
colour.write(f' New size: [CYAN:{formatted_new_size}]')
|
|
236
236
|
elif deleted_file:
|
|
237
|
-
colour.write(' Original size: [CYAN
|
|
237
|
+
colour.write(f' Original size: [CYAN:{formatted_old_size}]')
|
|
238
238
|
elif new_size == old_size:
|
|
239
|
-
colour.write(' Size: [CYAN]
|
|
239
|
+
colour.write(f' Size: [CYAN]{formatted_new_size}[NORMAL] (no change)')
|
|
240
240
|
else:
|
|
241
241
|
formatted_delta_size = files.format_size(abs(new_size - old_size), always_suffix=True)
|
|
242
242
|
|
|
@@ -297,9 +297,12 @@ def main():
|
|
|
297
297
|
|
|
298
298
|
if not deleted_file or not skip_deleted:
|
|
299
299
|
try:
|
|
300
|
-
|
|
301
|
-
except
|
|
302
|
-
print('Diff failed
|
|
300
|
+
subprocess.run([difftool, args.old_file, args.new_file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
|
301
|
+
except subprocess.CalledProcessError as exc:
|
|
302
|
+
print(f'WARNING: Diff failed - status = {exc.returncode}')
|
|
303
|
+
except FileNotFoundError:
|
|
304
|
+
print(f'ERROR: Unable to locate diff tool {difftool}')
|
|
305
|
+
sys.exit(1)
|
|
303
306
|
|
|
304
307
|
# Separate reports with a blank line
|
|
305
308
|
|
skilleter_thingy/rpylint.py
CHANGED
|
@@ -10,8 +10,11 @@ import os
|
|
|
10
10
|
import sys
|
|
11
11
|
import argparse
|
|
12
12
|
import glob
|
|
13
|
+
import subprocess
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
################################################################################
|
|
16
|
+
|
|
17
|
+
PYLINT = 'pylint'
|
|
15
18
|
|
|
16
19
|
################################################################################
|
|
17
20
|
|
|
@@ -32,26 +35,30 @@ def main():
|
|
|
32
35
|
|
|
33
36
|
sourcefiles = []
|
|
34
37
|
|
|
35
|
-
# Use rgrep to find source files that have a Python 3
|
|
38
|
+
# Use rgrep to find source files that have a Python 3 hashbang
|
|
36
39
|
|
|
37
40
|
for entry in args.paths:
|
|
38
41
|
if os.path.isdir(entry):
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
result = subprocess.run(['rgrep', '-E', '--exclude-dir=.git', '-l', '#![[:space:]]*/usr/bin/(env[[:space:]])?python3'] + args.paths,
|
|
43
|
+
capture_output=True, check=False, text=True)
|
|
44
|
+
|
|
45
|
+
if result.returncode == 1:
|
|
46
|
+
sys.stderr.write('No Python3 source files found\n')
|
|
47
|
+
sys.exit(2)
|
|
48
|
+
|
|
49
|
+
if result.returncode:
|
|
50
|
+
sys.stderr.write(f'ERROR #{result.returncode}: {result.stderr}')
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
sourcefiles += result.stdout.split('\n')
|
|
54
|
+
|
|
48
55
|
elif os.path.isfile(entry):
|
|
49
56
|
sourcefiles.append(entry)
|
|
50
57
|
else:
|
|
51
58
|
files = glob.glob(entry)
|
|
52
59
|
|
|
53
60
|
if not files:
|
|
54
|
-
sys.stderr.write('No files found matching "
|
|
61
|
+
sys.stderr.write(f'No files found matching "{entry}"')
|
|
55
62
|
sys.exit(2)
|
|
56
63
|
|
|
57
64
|
sourcefiles += files
|
|
@@ -59,18 +66,19 @@ def main():
|
|
|
59
66
|
# Run pylint on all the files
|
|
60
67
|
|
|
61
68
|
try:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
result = subprocess.run([PYLINT, '--output-format', 'parseable'] + sourcefiles, capture_output=False, check=False, text=True)
|
|
70
|
+
|
|
71
|
+
except FileNotFoundError:
|
|
72
|
+
sys.stderr.write(f'Unable to locate {PYLINT}\n')
|
|
73
|
+
sys.exit(1)
|
|
67
74
|
|
|
68
|
-
if
|
|
69
|
-
sys.stderr.write('Unexpected error:
|
|
75
|
+
if result.returncode >= 64:
|
|
76
|
+
sys.stderr.write(f'Unexpected error: {result.returncode}\n')
|
|
77
|
+
sys.exit(3)
|
|
70
78
|
|
|
71
79
|
# Function return code is the status return from pylint
|
|
72
80
|
|
|
73
|
-
return
|
|
81
|
+
return result.returncode
|
|
74
82
|
|
|
75
83
|
################################################################################
|
|
76
84
|
|
skilleter_thingy/tfm.py
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"""
|
|
13
13
|
################################################################################
|
|
14
14
|
|
|
15
|
-
import thingy.
|
|
15
|
+
import thingy.run as run
|
|
16
16
|
|
|
17
17
|
################################################################################
|
|
18
18
|
|
|
@@ -33,9 +33,9 @@ def instances(all=False):
|
|
|
33
33
|
|
|
34
34
|
instances_list = []
|
|
35
35
|
try:
|
|
36
|
-
for result in
|
|
36
|
+
for result in run.run(cmd):
|
|
37
37
|
instances_list.append(result)
|
|
38
|
-
except
|
|
38
|
+
except run.RunError as exc:
|
|
39
39
|
raise DockerError(exc)
|
|
40
40
|
|
|
41
41
|
return instances_list
|
|
@@ -48,8 +48,8 @@ def stop(instance, force=False):
|
|
|
48
48
|
# TODO: force option not implemented
|
|
49
49
|
|
|
50
50
|
try:
|
|
51
|
-
|
|
52
|
-
except
|
|
51
|
+
run.run(['docker', 'stop', instance], output=True)
|
|
52
|
+
except run.RunError as exc:
|
|
53
53
|
raise DockerError(exc)
|
|
54
54
|
|
|
55
55
|
################################################################################
|
|
@@ -65,8 +65,8 @@ def rm(instance, force=False):
|
|
|
65
65
|
cmd.append(instance)
|
|
66
66
|
|
|
67
67
|
try:
|
|
68
|
-
|
|
69
|
-
except
|
|
68
|
+
run.run(cmd, output=True)
|
|
69
|
+
except run.RunError as exc:
|
|
70
70
|
raise DockerError(exc)
|
|
71
71
|
|
|
72
72
|
################################################################################
|
|
@@ -75,9 +75,9 @@ def images():
|
|
|
75
75
|
""" Return a list of all current Docker images """
|
|
76
76
|
|
|
77
77
|
try:
|
|
78
|
-
for result in
|
|
78
|
+
for result in run.run(['docker', 'images', '-q']):
|
|
79
79
|
yield result
|
|
80
|
-
except
|
|
80
|
+
except run.RunError as exc:
|
|
81
81
|
raise DockerError(exc)
|
|
82
82
|
|
|
83
83
|
################################################################################
|
|
@@ -92,6 +92,6 @@ def rmi(image, force=False):
|
|
|
92
92
|
cmd.append(image)
|
|
93
93
|
|
|
94
94
|
try:
|
|
95
|
-
|
|
96
|
-
except
|
|
95
|
+
run.run(cmd, foreground=True)
|
|
96
|
+
except run.RunError as exc:
|
|
97
97
|
raise DockerError(exc)
|
skilleter_thingy/thingy/files.py
CHANGED
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
13
|
import shutil
|
|
14
|
-
|
|
15
|
-
import thingy.process as process
|
|
14
|
+
import subprocess
|
|
16
15
|
|
|
17
16
|
################################################################################
|
|
18
17
|
|
|
@@ -31,7 +30,7 @@ def file_type(filename, mime=False):
|
|
|
31
30
|
# is not a file, so we have to do that.
|
|
32
31
|
|
|
33
32
|
if not os.path.isfile(filename) or not os.access(filename, os.R_OK):
|
|
34
|
-
raise
|
|
33
|
+
raise FileNotFoundError('Unable to access %s' % filename)
|
|
35
34
|
|
|
36
35
|
cmd = ['file', '--brief']
|
|
37
36
|
|
|
@@ -40,8 +39,9 @@ def file_type(filename, mime=False):
|
|
|
40
39
|
|
|
41
40
|
cmd.append(filename)
|
|
42
41
|
|
|
43
|
-
result =
|
|
44
|
-
|
|
42
|
+
result = subprocess.run(cmd, capture_output=True, check=False, text=True)
|
|
43
|
+
|
|
44
|
+
return result.stdout.split('\n')[0] if result.returncode==0 else None
|
|
45
45
|
|
|
46
46
|
################################################################################
|
|
47
47
|
|
|
@@ -148,7 +148,7 @@ if __name__ == "__main__":
|
|
|
148
148
|
for mimeflag in (False, True):
|
|
149
149
|
print('/bin/sh is: %s' % file_type('/bin/sh', mimeflag))
|
|
150
150
|
print('/bin/dash is: %s' % file_type('/bin/dash', mimeflag))
|
|
151
|
-
print('
|
|
151
|
+
print('multigit.py is: %s' % file_type('multigit.py', mimeflag))
|
|
152
152
|
print('')
|
|
153
153
|
|
|
154
154
|
for sizevalue in (0, 1, 999, 1024, 1025, 1.3 * 1024, 2**32 - 1, 2**64 + 2**49):
|
skilleter_thingy/thingy/git.py
CHANGED
|
@@ -951,7 +951,7 @@ def ref(fields=('objectname'), sort=None, remotes=False, path=None):
|
|
|
951
951
|
def branches(all=False, path=None):
|
|
952
952
|
""" Return a list of all the branches in the current repo """
|
|
953
953
|
|
|
954
|
-
cmd = ['branch']
|
|
954
|
+
cmd = ['branch', '--format=%(refname:short)','--list']
|
|
955
955
|
|
|
956
956
|
if all:
|
|
957
957
|
cmd.append('--all')
|
|
@@ -959,7 +959,7 @@ def branches(all=False, path=None):
|
|
|
959
959
|
results = []
|
|
960
960
|
for output in git(cmd, path=path):
|
|
961
961
|
if ' -> ' not in output and '(HEAD detached at ' not in output:
|
|
962
|
-
results.append(output
|
|
962
|
+
results.append(output)
|
|
963
963
|
|
|
964
964
|
return results
|
|
965
965
|
|
skilleter_thingy/thingy/popup.py
CHANGED
|
@@ -14,6 +14,7 @@ the popup again.
|
|
|
14
14
|
|
|
15
15
|
import time
|
|
16
16
|
import curses
|
|
17
|
+
import curses.panel
|
|
17
18
|
|
|
18
19
|
################################################################################
|
|
19
20
|
|
|
@@ -71,7 +72,7 @@ class PopUp():
|
|
|
71
72
|
curses.panel.update_panels()
|
|
72
73
|
self.screen.refresh()
|
|
73
74
|
|
|
74
|
-
def __exit__(self,
|
|
75
|
+
def __exit__(self, _exc_type, _exc_value, _exc_traceback):
|
|
75
76
|
""" Remove the popup """
|
|
76
77
|
|
|
77
78
|
if self.panel:
|
skilleter_thingy/thingy/run.py
CHANGED
|
@@ -37,108 +37,125 @@ class RunError(Exception):
|
|
|
37
37
|
|
|
38
38
|
################################################################################
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
when the specified command (cmd, which is presumed to be the process
|
|
45
|
-
outputting to the input stream) exits.
|
|
46
|
-
TODO: Use of convert_ansi should be controlled via a parameter (off/light/dark)
|
|
47
|
-
TODO: Another issue is that readline() only returns at EOF or EOL, so if you get a prompt "Continue?" with no newline you do not see it until after you respond to it.
|
|
40
|
+
# TODO: This is the _process() and run() replacement once additional parameters have been implemented
|
|
41
|
+
# as those functions are probably over-specified, so need to work out what functionality is ACTUALLY being used!
|
|
42
|
+
|
|
43
|
+
def command(cmd, show_stdout=False, show_stderr=False):
|
|
48
44
|
"""
|
|
45
|
+
Run an external command and optionally stream its stdout/stderr to the console
|
|
46
|
+
while capturing them.
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
Args:
|
|
49
|
+
cmd: Command to run (string or argv list). If a string, it will be split with shlex.split().
|
|
50
|
+
show_stdout: If True, echo stdout lines to the console as they arrive.
|
|
51
|
+
show_stderr: If True, echo stderr lines to the console as they arrive.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if isinstance(stream, list):
|
|
57
|
-
stream.append(output.rstrip())
|
|
58
|
-
else:
|
|
59
|
-
if stream in (sys.stdout, sys.stderr):
|
|
60
|
-
stream.write(tidy.convert_ansi(output))
|
|
61
|
-
elif ansi_clean:
|
|
62
|
-
stream.write(tidy.remove_ansi(output))
|
|
63
|
-
else:
|
|
64
|
-
stream.write(output)
|
|
53
|
+
Returns:
|
|
54
|
+
(returncode, stdout_lines, stderr_lines)
|
|
55
|
+
"""
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
def _pump(stream, sink, echo):
|
|
58
|
+
"""Thread to capture and optionally echo output from subprocess.Popen"""
|
|
59
|
+
|
|
60
|
+
# Read line-by-line until EOF
|
|
61
|
+
|
|
62
|
+
for line in iter(stream.readline, ""):
|
|
63
|
+
# Strip trailing newline when storing; keep original when echoing
|
|
64
|
+
sink.append(line.rstrip("\n"))
|
|
65
|
+
|
|
66
|
+
if echo:
|
|
67
|
+
print(tidy.convert_ansi(line), end="", flush=True)
|
|
68
|
+
|
|
69
|
+
stream.close()
|
|
70
|
+
|
|
71
|
+
# Normalize command to be a string
|
|
72
|
+
|
|
73
|
+
if isinstance(cmd, str):
|
|
74
|
+
cmd = shlex.split(cmd)
|
|
75
|
+
|
|
76
|
+
# Storage for stdout/stderr
|
|
77
|
+
|
|
78
|
+
stdout_lines = []
|
|
79
|
+
stderr_lines = []
|
|
80
|
+
|
|
81
|
+
# Start process with separate pipes; line-buffering for timely reads
|
|
82
|
+
|
|
83
|
+
proc = subprocess.Popen(
|
|
84
|
+
cmd,
|
|
85
|
+
stdout=subprocess.PIPE,
|
|
86
|
+
stderr=subprocess.PIPE,
|
|
87
|
+
text=True, # decode to str
|
|
88
|
+
bufsize=1, # line-buffered
|
|
89
|
+
universal_newlines=True, # compatibility alias
|
|
90
|
+
errors="replace" # avoid crashes on decoding issues
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Threads to read both streams concurrently (prevents deadlocks)
|
|
94
|
+
|
|
95
|
+
t_out = threading.Thread(target=_pump, args=(proc.stdout, stdout_lines, show_stdout))
|
|
96
|
+
t_err = threading.Thread(target=_pump, args=(proc.stderr, stderr_lines, show_stderr))
|
|
97
|
+
|
|
98
|
+
t_out.start()
|
|
99
|
+
t_err.start()
|
|
100
|
+
|
|
101
|
+
# Wait for process to complete and threads to drain
|
|
102
|
+
|
|
103
|
+
returncode = proc.wait()
|
|
104
|
+
t_out.join()
|
|
105
|
+
t_err.join()
|
|
106
|
+
|
|
107
|
+
# Return the status, stdout and stderr
|
|
108
|
+
|
|
109
|
+
return returncode, stdout_lines, stderr_lines
|
|
68
110
|
|
|
69
111
|
################################################################################
|
|
70
112
|
|
|
71
|
-
def
|
|
113
|
+
def capture_output(cmd, input_stream, output_streams):
|
|
72
114
|
""" Capture data from a stream (input_stream), optionally
|
|
73
115
|
outputting it (if output_streams is not None and optionally
|
|
74
116
|
saving it into a variable (data, if not None), terminating
|
|
75
117
|
when the specified command (cmd, which is presumed to be the process
|
|
76
118
|
outputting to the input stream) exits.
|
|
77
119
|
TODO: Use of convert_ansi should be controlled via a parameter (off/light/dark)
|
|
78
|
-
TODO:
|
|
120
|
+
TODO: Another issue is that readline() only returns at EOF or EOL, so if you get a prompt "Continue?" with no newline you do not see it until after you respond to it.
|
|
79
121
|
"""
|
|
80
122
|
|
|
81
|
-
output_buffer = []
|
|
82
|
-
|
|
83
123
|
while True:
|
|
84
|
-
output = input_stream.
|
|
124
|
+
output = input_stream.readline()
|
|
85
125
|
|
|
86
126
|
if output:
|
|
87
127
|
if output_streams:
|
|
88
128
|
for stream in output_streams:
|
|
89
129
|
if isinstance(stream, list):
|
|
90
|
-
|
|
91
|
-
stream.append(''.join(output_buffer))
|
|
92
|
-
output_buffer = []
|
|
93
|
-
else:
|
|
94
|
-
output_buffer.append(output)
|
|
130
|
+
stream.append(output.rstrip())
|
|
95
131
|
else:
|
|
96
|
-
stream.
|
|
97
|
-
|
|
132
|
+
if stream in (sys.stdout, sys.stderr):
|
|
133
|
+
stream.write(tidy.convert_ansi(output))
|
|
134
|
+
else:
|
|
135
|
+
stream.write(output)
|
|
98
136
|
|
|
99
137
|
elif cmd.poll() is not None:
|
|
100
|
-
if output_buffer:
|
|
101
|
-
stream.append(''.join(output_buffer))
|
|
102
|
-
|
|
103
138
|
return
|
|
104
139
|
|
|
105
140
|
################################################################################
|
|
106
141
|
|
|
107
|
-
def
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
shell=False,
|
|
111
|
-
output=None,
|
|
112
|
-
ansi_clean=False,
|
|
113
|
-
exception=True,
|
|
114
|
-
continuous=False):
|
|
142
|
+
def _process(command,
|
|
143
|
+
stdout=None, stderr=None,
|
|
144
|
+
output=None):
|
|
115
145
|
""" Run an external command.
|
|
116
146
|
|
|
117
147
|
stdout and stderr indicate whether stdout/err are output and/or sent to a file and/or stored in a variable.
|
|
118
148
|
They can be boolean (True: output to sys.stdout/err, False: Do nothing), a file handle or a variable, or an
|
|
119
149
|
array of any number of these (except booleans).
|
|
120
150
|
|
|
121
|
-
return_stdout and return_stderr indicate whether stdout/err should be returned from the function (setting
|
|
122
|
-
these to False saves memory if the output is not required).
|
|
123
|
-
|
|
124
|
-
If shell is True the command will be run in a shell and wildcard arguments expanded
|
|
125
|
-
|
|
126
|
-
If exception is True an exception will be raised if the command returns a non-zero status
|
|
127
|
-
|
|
128
151
|
If output is True then stdout and stderr are both output as if stdout=True and stderr=True (in addition to
|
|
129
152
|
any other values passed in those parameters)
|
|
130
153
|
|
|
131
|
-
If ansi_clean is True then ANSI control sequences are removed from any streams in stdout and stderr but
|
|
132
|
-
not from the console output.
|
|
133
|
-
|
|
134
|
-
If continuous is True then output is processed character-by-character (normally for use when output=True)
|
|
135
|
-
TODO: Currently this causes the ansi_clean option to be ignored
|
|
136
|
-
|
|
137
154
|
The return value is a tuple consisting of the status code, captured stdout (if any) and captured
|
|
138
155
|
stderr (if any).
|
|
139
156
|
|
|
140
|
-
Will raise OSError if the command could not be run and RunError if
|
|
141
|
-
|
|
157
|
+
Will raise OSError if the command could not be run and RunError if the command returned a non-zero status code.
|
|
158
|
+
"""
|
|
142
159
|
|
|
143
160
|
# If stdout/stderr are booleans then output to stdout/stderr if True, else discard output
|
|
144
161
|
|
|
@@ -165,26 +182,16 @@ def process(command,
|
|
|
165
182
|
if sys.stderr not in stderr:
|
|
166
183
|
stderr.append(sys.stderr)
|
|
167
184
|
|
|
168
|
-
# Capture stdout/stderr to arrays
|
|
185
|
+
# Capture stdout/stderr to arrays
|
|
169
186
|
|
|
170
187
|
stdout_data = []
|
|
171
188
|
stderr_data = []
|
|
172
189
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if return_stderr:
|
|
177
|
-
stderr.append(stderr_data)
|
|
190
|
+
stdout.append(stdout_data)
|
|
191
|
+
stderr.append(stderr_data)
|
|
178
192
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if shell:
|
|
183
|
-
if not isinstance(command, str):
|
|
184
|
-
command = ' '.join(command)
|
|
185
|
-
else:
|
|
186
|
-
if isinstance(command, str):
|
|
187
|
-
command = shlex.split(command, comments=True)
|
|
193
|
+
if isinstance(command, str):
|
|
194
|
+
command = shlex.split(command, comments=True)
|
|
188
195
|
|
|
189
196
|
# Use a pipe for stdout/stderr if are are capturing it
|
|
190
197
|
# and send it to /dev/null if we don't care about it at all.
|
|
@@ -206,19 +213,18 @@ def process(command,
|
|
|
206
213
|
stderr=stderr_stream,
|
|
207
214
|
text=True,
|
|
208
215
|
errors='ignore',
|
|
209
|
-
encoding='ascii'
|
|
210
|
-
shell=shell)
|
|
216
|
+
encoding='ascii')
|
|
211
217
|
|
|
212
218
|
# Create threads to capture stderr and/or stdout if necessary
|
|
213
219
|
|
|
214
220
|
if stdout_stream == subprocess.PIPE:
|
|
215
|
-
stdout_thread = threading.Thread(target=
|
|
221
|
+
stdout_thread = threading.Thread(target=capture_output, args=(cmd, cmd.stdout, stdout), daemon=True)
|
|
216
222
|
stdout_thread.start()
|
|
217
223
|
else:
|
|
218
224
|
stdout_thread = None
|
|
219
225
|
|
|
220
226
|
if stderr_stream == subprocess.PIPE:
|
|
221
|
-
stderr_thread = threading.Thread(target=
|
|
227
|
+
stderr_thread = threading.Thread(target=capture_output, args=(cmd, cmd.stderr, stderr), daemon=True)
|
|
222
228
|
stderr_thread.start()
|
|
223
229
|
else:
|
|
224
230
|
stderr_thread = None
|
|
@@ -233,13 +239,10 @@ def process(command,
|
|
|
233
239
|
|
|
234
240
|
cmd.wait()
|
|
235
241
|
|
|
236
|
-
# If the command failed, raise an exception
|
|
242
|
+
# If the command failed, raise an exception
|
|
237
243
|
|
|
238
|
-
if
|
|
239
|
-
if
|
|
240
|
-
raise RunError('\n'.join(stderr_data))
|
|
241
|
-
else:
|
|
242
|
-
raise RunError('Error %d running "%s"' % (cmd.returncode, (command if isinstance(command, str) else ' '.join(command))))
|
|
244
|
+
if cmd.returncode:
|
|
245
|
+
raise RunError('\n'.join(stderr_data) if stderr_data else 'Error %d running "%s"' % (cmd.returncode, ' '.join(command)))
|
|
243
246
|
|
|
244
247
|
# Return status, stdout, stderr (the latter 2 may be empty if we did not capture data).
|
|
245
248
|
|
|
@@ -248,107 +251,34 @@ def process(command,
|
|
|
248
251
|
################################################################################
|
|
249
252
|
|
|
250
253
|
def run(command,
|
|
251
|
-
stdout=None,
|
|
252
|
-
|
|
253
|
-
output=None
|
|
254
|
-
|
|
255
|
-
exception=True,
|
|
256
|
-
continuous=False):
|
|
257
|
-
""" Simple interface to the process() function
|
|
254
|
+
stdout=None,
|
|
255
|
+
stderr=None,
|
|
256
|
+
output=None):
|
|
257
|
+
""" Simple interface to the _process() function
|
|
258
258
|
Has the same parameters, with the same defaults.
|
|
259
259
|
The return value is either the data output to stdout, if any
|
|
260
260
|
or the data output to stderr otherwise.
|
|
261
261
|
The status code is not returned, but the function will raise an exception
|
|
262
|
-
|
|
262
|
+
if it is non-zero """
|
|
263
263
|
|
|
264
|
-
result =
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
output=output,
|
|
269
|
-
ansi_clean=ansi_clean,
|
|
270
|
-
exception=exception,
|
|
271
|
-
continuous=continuous)
|
|
264
|
+
result = _process(command=command,
|
|
265
|
+
stdout=stdout,
|
|
266
|
+
stderr=stderr,
|
|
267
|
+
output=output)
|
|
272
268
|
|
|
273
269
|
return result['stdout'] if result['stdout'] else result['stderr']
|
|
274
270
|
|
|
275
271
|
################################################################################
|
|
276
272
|
|
|
277
|
-
def status(command, shell=False, output=False):
|
|
278
|
-
""" Alternative simple interface to the process() function
|
|
279
|
-
Just takes a command and the shell flag.
|
|
280
|
-
Runs the command without capturing the output
|
|
281
|
-
Optionally outputting both stdout and stderr
|
|
282
|
-
and returns the status code.
|
|
283
|
-
Will only raise an exception if the command could not be run. """
|
|
284
|
-
|
|
285
|
-
return process(command,
|
|
286
|
-
stdout=output,
|
|
287
|
-
stderr=output,
|
|
288
|
-
shell=shell,
|
|
289
|
-
exception=False)['status']
|
|
290
|
-
|
|
291
|
-
################################################################################
|
|
292
|
-
# Legacy compatibility layer for process.py API
|
|
293
|
-
|
|
294
|
-
def run_process(command, foreground=False, shell=False):
|
|
295
|
-
"""
|
|
296
|
-
Legacy compatibility function for process.py API.
|
|
297
|
-
|
|
298
|
-
Args:
|
|
299
|
-
command: Command to run (string or list)
|
|
300
|
-
foreground: If True, run in foreground with output to console
|
|
301
|
-
shell: Whether to use shell for execution
|
|
302
|
-
|
|
303
|
-
Returns:
|
|
304
|
-
List of output lines (empty if foreground=True)
|
|
305
|
-
|
|
306
|
-
Raises:
|
|
307
|
-
RunError: If command fails
|
|
308
|
-
"""
|
|
309
|
-
if foreground:
|
|
310
|
-
# For foreground mode, output directly to console
|
|
311
|
-
try:
|
|
312
|
-
status_result = status(command, shell=shell, output=True)
|
|
313
|
-
if status_result != 0:
|
|
314
|
-
raise RunError(f"Command failed with return code {status_result}")
|
|
315
|
-
return [] # process.py returns empty list for foreground mode
|
|
316
|
-
except Exception as e:
|
|
317
|
-
if isinstance(e, RunError):
|
|
318
|
-
raise
|
|
319
|
-
raise RunError(f"Command failed: {str(e)}")
|
|
320
|
-
else:
|
|
321
|
-
# For background mode, capture and return output
|
|
322
|
-
try:
|
|
323
|
-
result = run(command, shell=shell, exception=True)
|
|
324
|
-
if isinstance(result, list):
|
|
325
|
-
return [line.rstrip() for line in result if line.strip()]
|
|
326
|
-
elif isinstance(result, str):
|
|
327
|
-
return [line.rstrip() for line in result.splitlines() if line.strip()]
|
|
328
|
-
return []
|
|
329
|
-
except Exception as e:
|
|
330
|
-
if isinstance(e, RunError):
|
|
331
|
-
raise
|
|
332
|
-
raise RunError(f"Command failed: {str(e)}")
|
|
333
|
-
|
|
334
|
-
################################################################################
|
|
335
|
-
|
|
336
273
|
if __name__ == '__main__':
|
|
337
274
|
def test_run(cmd,
|
|
338
|
-
stdout=None, stderr=None
|
|
339
|
-
return_stdout=True, return_stderr=True,
|
|
340
|
-
shell=False,
|
|
341
|
-
exception=True):
|
|
275
|
+
stdout=None, stderr=None):
|
|
342
276
|
""" Test wrapper for the process() function. """
|
|
343
277
|
|
|
344
278
|
print('-' * 80)
|
|
345
279
|
print('Running: %s' % (cmd if isinstance(cmd, str) else ' '.join(cmd)))
|
|
346
280
|
|
|
347
|
-
result =
|
|
348
|
-
stdout=stdout, stderr=stderr,
|
|
349
|
-
return_stdout=return_stdout, return_stderr=return_stderr,
|
|
350
|
-
shell=shell,
|
|
351
|
-
exception=exception)
|
|
281
|
+
result = _process(cmd, stdout=stdout, stderr=stderr)
|
|
352
282
|
|
|
353
283
|
print('Status: %d' % result['status'])
|
|
354
284
|
|
|
@@ -358,19 +288,19 @@ if __name__ == '__main__':
|
|
|
358
288
|
test_run('echo nothing')
|
|
359
289
|
|
|
360
290
|
test_run(['ls', '-l', 'run_jed'])
|
|
361
|
-
test_run(['ls -l run_*'], stdout=True
|
|
362
|
-
test_run('false'
|
|
363
|
-
test_run('true', stdout=sys.stdout
|
|
364
|
-
test_run(['git', 'status'], stdout=sys.stdout, stderr=sys.stderr
|
|
291
|
+
test_run(['ls -l run_*'], stdout=True)
|
|
292
|
+
test_run('false')
|
|
293
|
+
test_run('true', stdout=sys.stdout)
|
|
294
|
+
test_run(['git', 'status'], stdout=sys.stdout, stderr=sys.stderr)
|
|
365
295
|
|
|
366
|
-
test_run(['make'], stderr=sys.stderr
|
|
367
|
-
test_run(['make'], stdout=sys.stdout, stderr=[sys.stderr]
|
|
368
|
-
test_run(['make'], stdout=True
|
|
369
|
-
test_run(['make'], stdout=sys.stdout
|
|
370
|
-
test_run(['make']
|
|
296
|
+
test_run(['make'], stderr=sys.stderr)
|
|
297
|
+
test_run(['make'], stdout=sys.stdout, stderr=[sys.stderr])
|
|
298
|
+
test_run(['make'], stdout=True)
|
|
299
|
+
test_run(['make'], stdout=sys.stdout)
|
|
300
|
+
test_run(['make'])
|
|
371
301
|
|
|
372
302
|
output = []
|
|
373
|
-
test_run('ls -l x*; sleep 1; echo "Bye!"', stderr=[sys.stderr, output], stdout=sys.stdout
|
|
303
|
+
test_run('ls -l x*; sleep 1; echo "Bye!"', stderr=[sys.stderr, output], stdout=sys.stdout)
|
|
374
304
|
print('Output=%s' % output)
|
|
375
305
|
|
|
376
306
|
test()
|
skilleter_thingy/x.py
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skilleter_thingy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: A collection of useful utilities, mainly aimed at making Git more friendly
|
|
5
5
|
Author-email: John Skilleter <john@skilleter.org.uk>
|
|
6
6
|
Project-URL: Home, https://skilleter.org.uk
|
|
@@ -2,22 +2,22 @@ skilleter_thingy/__init__.py,sha256=rVPTxm8L5w52U0YdTd7r_D44SBP7pS3JCJtsf0iIsow,
|
|
|
2
2
|
skilleter_thingy/addpath.py,sha256=4Yhhgjjz1XDI98j0dAiQpNA2ejLefeWUTeSg3nIXQq0,3842
|
|
3
3
|
skilleter_thingy/console_colours.py,sha256=BOS9mo3jChx_FE8L1j488MDoVNgib11KjTRhrz_YRYE,1781
|
|
4
4
|
skilleter_thingy/docker_purge.py,sha256=PRQ7EBXymjYIHuJL4pk4r6KNn09IF28OGZ0ln57xtNg,3314
|
|
5
|
-
skilleter_thingy/ffind.py,sha256=
|
|
5
|
+
skilleter_thingy/ffind.py,sha256=kIMx3VDvNpvTbp0gDzsiOiSlV_R8xZowHxADjts9qI0,19571
|
|
6
6
|
skilleter_thingy/ggit.py,sha256=BL-DhNcz4Nd3sA-3Kl6gZ-zFtbNqOpyufvas-0aD8nk,2465
|
|
7
7
|
skilleter_thingy/ggrep.py,sha256=fnTzOI1Qbf7IY_TnStdx5uqeUhqSDkapxmhYgrONJHw,5887
|
|
8
8
|
skilleter_thingy/git_br.py,sha256=QdFU5HPLoV4YKX0bVLX_j_6Kv00TDenU6o3_ufbQAq8,6004
|
|
9
9
|
skilleter_thingy/git_ca.py,sha256=nkbxPS0BZQ8WXsiqf7dbcUyKXus6Bz4MSJ5olCWNUQQ,5168
|
|
10
|
-
skilleter_thingy/git_cleanup.py,sha256=
|
|
10
|
+
skilleter_thingy/git_cleanup.py,sha256=3T8vkDJHsEVMTMkC2ARo8KHJ4zgPo6mmABVeuHcXheY,10519
|
|
11
11
|
skilleter_thingy/git_co.py,sha256=Mc-6jEUpVWAJJ-2PTpQ4tjDw03_zJMJDX9SGIxCqzJQ,8404
|
|
12
12
|
skilleter_thingy/git_common.py,sha256=FKXB6aT-y_a3N6wFnnwM6qJi4ClLFvDAivkSQ4iEYP4,2111
|
|
13
13
|
skilleter_thingy/git_hold.py,sha256=coyHdl1bWivrWdmgs7smVPvHRNoXgsgmUjR6n-08lV4,4920
|
|
14
14
|
skilleter_thingy/git_mr.py,sha256=MsrAkIKW27fVTljV__zAjMveIpufvDQ_j0jeKJu2rZM,3426
|
|
15
15
|
skilleter_thingy/git_parent.py,sha256=VqP4v3zsWp6utJvxFxSB_GwCv82xNIiiBlAlkinO1Wk,2938
|
|
16
16
|
skilleter_thingy/git_retag.py,sha256=JT-yD-uU4dL0mxDq9IRynugUKqIxjMCdU1dYDjiBSTU,1828
|
|
17
|
-
skilleter_thingy/git_review.py,sha256=
|
|
18
|
-
skilleter_thingy/git_update.py,sha256=
|
|
17
|
+
skilleter_thingy/git_review.py,sha256=AsCVq2viAsNE527uHvbPOYASI3iVx0JgCfuA0J0Ocmg,52557
|
|
18
|
+
skilleter_thingy/git_update.py,sha256=Ic5X_GZO2yez_nwrCqSTq3p07k3TyRAMSvQYpjP-yvE,14571
|
|
19
19
|
skilleter_thingy/git_wt.py,sha256=tkGN_Bfz80icHNDVG8xuXVeUUR-xyZ3u8jopLRt1Ff4,2355
|
|
20
|
-
skilleter_thingy/gitcmp_helper.py,sha256=
|
|
20
|
+
skilleter_thingy/gitcmp_helper.py,sha256=SwYYHXYJQnPizCrLpHwOt-h0_O61zRwV1Ckr2XehyKc,11576
|
|
21
21
|
skilleter_thingy/gitprompt.py,sha256=19vBe2JSs6H_7us5MYmdmGnwEIKiP9K2JlUBWPxXuCg,8939
|
|
22
22
|
skilleter_thingy/gl.py,sha256=9zbGpKxw6lX9RghLkdy-Q5sZlqtbB3uGFO04qTu1dH8,5954
|
|
23
23
|
skilleter_thingy/linecount.py,sha256=ehTN6VD76i4U5k6dXuYoiqSRHI67_BP-bziklNAJSKY,4309
|
|
@@ -26,12 +26,13 @@ skilleter_thingy/py_audit.py,sha256=4CAdqBAIIVcpTCn_7dGm56bdfGpUtUJofqTGZomClkY,
|
|
|
26
26
|
skilleter_thingy/readable.py,sha256=LcMMOiuzf9j5TsxcMbO0sbj6m1QCuABl91Hrv-YyIww,15422
|
|
27
27
|
skilleter_thingy/remdir.py,sha256=Ueg3a6_m7y50zWykhKk6pcuz4FKPjoLJVPo9gh_dsic,4653
|
|
28
28
|
skilleter_thingy/rmdupe.py,sha256=RWtOHq__zY4yOf6_Y-H-8RRJy31Sr3c8DEyTd6Y4oV4,17213
|
|
29
|
-
skilleter_thingy/rpylint.py,sha256=
|
|
29
|
+
skilleter_thingy/rpylint.py,sha256=spdVVpNyElkV1fQknv-RESmqe7U0QZYneX96vSAEMSo,2875
|
|
30
30
|
skilleter_thingy/strreplace.py,sha256=zMhqC38KF0BddTsRM5Pa99HU3KXvxXg942qxRK-LALA,2539
|
|
31
|
-
skilleter_thingy/tfm.py,sha256=
|
|
31
|
+
skilleter_thingy/tfm.py,sha256=bw_S4bCAisZAEkzrbqnXqJsjC62oA08FM_xrbkuDQuQ,33787
|
|
32
32
|
skilleter_thingy/tfparse.py,sha256=rRoinnbq6sLfkT38yzzXi2jQuJgBIJoC--G05TVTDIc,2991
|
|
33
33
|
skilleter_thingy/trimpath.py,sha256=ijLowl-rxV53m0G75tGNuHWobObz5NreBy8yXP9l4eY,2373
|
|
34
34
|
skilleter_thingy/venv_create.py,sha256=EV_oZh3JlDc5hX5h9T1hnt65AEABw6PufaKvPYabR00,1159
|
|
35
|
+
skilleter_thingy/x.py,sha256=hFiinFX2p0x0OkPf7QnBdW6vAhSIfocwq4siZl2_MvQ,49
|
|
35
36
|
skilleter_thingy/xchmod.py,sha256=T89xiH_po0nvH5T1AGgQOD5yhjKd9-LcHcmez3IORww,4604
|
|
36
37
|
skilleter_thingy/yamlcheck.py,sha256=FXylZ5NtHirDlPVhVEUZUZkTugVR-g51BbjaN06akAc,2868
|
|
37
38
|
skilleter_thingy/thingy/__init__.py,sha256=rVPTxm8L5w52U0YdTd7r_D44SBP7pS3JCJtsf0iIsow,110
|
|
@@ -40,20 +41,19 @@ skilleter_thingy/thingy/dc_curses.py,sha256=fuuQPR11zV_akAhygL_cAhVLC5YAgKgowzlI
|
|
|
40
41
|
skilleter_thingy/thingy/dc_defaults.py,sha256=ahcteQvoWZrO5iTU68zkIY1Zex6iX5uR5ubwI4CCYBk,6170
|
|
41
42
|
skilleter_thingy/thingy/dc_util.py,sha256=Df73imXhHx3HzcPHiRcHAoea0e3HURdLcrolUsMhOFs,1783
|
|
42
43
|
skilleter_thingy/thingy/dircolors.py,sha256=aBcq9ci855GSOIjrZWm8kG0ksCodvUmc4FlIOEOyBcA,12292
|
|
43
|
-
skilleter_thingy/thingy/docker.py,sha256=
|
|
44
|
-
skilleter_thingy/thingy/files.py,sha256=
|
|
45
|
-
skilleter_thingy/thingy/git.py,sha256=
|
|
44
|
+
skilleter_thingy/thingy/docker.py,sha256=iT8PqX2hJfcR1e4hotQfSBBYNe0Qdcmeo-XJ6y7lw7Y,2477
|
|
45
|
+
skilleter_thingy/thingy/files.py,sha256=nFIOEi2rl2SuYa6Zd7Nf1BWCKyKlF8D6hsbPlfnVefQ,4791
|
|
46
|
+
skilleter_thingy/thingy/git.py,sha256=xUIjRH3BLXssUbGdnxr7-UfGuiSai17jruNe7h9gBJw,43070
|
|
46
47
|
skilleter_thingy/thingy/gitlab.py,sha256=uXAF918xnPk6qQyiwPQDbMZfqtJzhiRqDS7yEtJEIAg,6079
|
|
47
48
|
skilleter_thingy/thingy/path.py,sha256=8uM2Q9zFRWv_SaVOX49PeecQXttl7J6lsmBuRXWsXKY,4732
|
|
48
|
-
skilleter_thingy/thingy/popup.py,sha256=
|
|
49
|
-
skilleter_thingy/thingy/
|
|
50
|
-
skilleter_thingy/thingy/run.py,sha256=mqafCzW9op_xKCt8OY3jJ6YltmoOJGh44vzl667mwws,14196
|
|
49
|
+
skilleter_thingy/thingy/popup.py,sha256=TY9rpj4q8uZxerSt641LGUTy0TZgUjgfEX-CkRMuyek,2540
|
|
50
|
+
skilleter_thingy/thingy/run.py,sha256=Q6uug_LucKbn36RB-r08QYaCzmeoU452ipzQ2YiVUPs,10593
|
|
51
51
|
skilleter_thingy/thingy/tfm_pane.py,sha256=XTTpSm71CyQyGmlVLuCthioOwech0jhUiFUXb-chS_Q,19792
|
|
52
52
|
skilleter_thingy/thingy/tidy.py,sha256=AQ2RawsZJg6WHrgayi_ZptFL9occ7suSdCHbU3P-cys,5971
|
|
53
53
|
skilleter_thingy/thingy/venv_template.py,sha256=ZfUvi8qFNGrk7J030Zy57xjwMtfIArJyqa-MqafyjVk,1016
|
|
54
|
-
skilleter_thingy-0.2.
|
|
55
|
-
skilleter_thingy-0.2.
|
|
56
|
-
skilleter_thingy-0.2.
|
|
57
|
-
skilleter_thingy-0.2.
|
|
58
|
-
skilleter_thingy-0.2.
|
|
59
|
-
skilleter_thingy-0.2.
|
|
54
|
+
skilleter_thingy-0.2.14.dist-info/licenses/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
|
|
55
|
+
skilleter_thingy-0.2.14.dist-info/METADATA,sha256=bA0ajwEyvjYdGEvO7KlRLoeZAfdRhudqkepZiTMHASk,28914
|
|
56
|
+
skilleter_thingy-0.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
57
|
+
skilleter_thingy-0.2.14.dist-info/entry_points.txt,sha256=MTNWf8jOx8Fy3tSwVLCZPlEyzlDF36odw-IN-cSefP8,1784
|
|
58
|
+
skilleter_thingy-0.2.14.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
|
|
59
|
+
skilleter_thingy-0.2.14.dist-info/RECORD,,
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
################################################################################
|
|
4
|
-
""" Legacy compatibility module for process.py API.
|
|
5
|
-
This module provides backward compatibility for existing code that used
|
|
6
|
-
the original process.py module. The actual implementation has been moved
|
|
7
|
-
to thingy.run with enhanced capabilities.
|
|
8
|
-
"""
|
|
9
|
-
################################################################################
|
|
10
|
-
|
|
11
|
-
from thingy.run import run_process, RunError
|
|
12
|
-
|
|
13
|
-
# Provide the same API as the original process.py
|
|
14
|
-
def run(command, foreground=False, shell=False):
|
|
15
|
-
"""
|
|
16
|
-
Run a command with the original process.py API.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
command: Command to run (string or list)
|
|
20
|
-
foreground: If True, run in foreground with output to console
|
|
21
|
-
shell: Whether to use shell for execution
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
List of output lines (empty if foreground=True)
|
|
25
|
-
|
|
26
|
-
Raises:
|
|
27
|
-
RunError: If command fails
|
|
28
|
-
"""
|
|
29
|
-
return run_process(command, foreground=foreground, shell=shell)
|
|
30
|
-
|
|
31
|
-
# Re-export RunError for compatibility
|
|
32
|
-
__all__ = ['run', 'RunError']
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|