skilleter-thingy 0.2.13__py3-none-any.whl → 0.2.15__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 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('%s\n' % msg)
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('%s\n' % exc)
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 = copy.copy(args.exec)
72
- cmd[cmd.index('^')] = filepath
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
- run.run(cmd, stdout=sys.stdout, stderr=sys.stderr)
80
- except run.RunError as exc:
81
- error(exc.msg)
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('%s\0' % filepath)
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 = '"%s"' % 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 "%s"' % t)
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
- if args.exec.count('^') > 1:
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
- run.run(diff_cmd)
505
- except Exception as exc:
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
 
@@ -105,7 +105,7 @@ def main():
105
105
  # Get the list of all local branches
106
106
 
107
107
  try:
108
- all_branches = [branch for branch in git.branches()]
108
+ all_branches = git.branches()
109
109
  except git.GitError as exc:
110
110
  colour.error(exc.msg, status=exc.status)
111
111
 
@@ -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, exc_type, exc_value, exc_traceback):
202
+ def __exit__(self, _exc_type, _exc_value, _exc_traceback):
204
203
  """ Remove the popup """
205
204
 
206
205
  if self.panel:
@@ -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 '%s' % ''.join(permtext)
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:%s] is not merged' % args.file_path)
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: %s\n' % args.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: %s\n' % args.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('(%d/%d)' % (path_count, path_total))
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 = (args.old_file == '/dev/null')
203
- deleted_file = (args.new_file == '/dev/null')
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:%s]' % formatted_new_size)
235
+ colour.write(f' New size: [CYAN:{formatted_new_size}]')
236
236
  elif deleted_file:
237
- colour.write(' Original size: [CYAN:%s]' % formatted_old_size)
237
+ colour.write(f' Original size: [CYAN:{formatted_old_size}]')
238
238
  elif new_size == old_size:
239
- colour.write(' Size: [CYAN]%s[NORMAL] (no change)' % formatted_new_size)
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
- run.run([difftool, args.old_file, args.new_file])
301
- except run.RunError as exc:
302
- print('Diff failed: %s' % exc.msg)
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
 
@@ -10,8 +10,11 @@ import os
10
10
  import sys
11
11
  import argparse
12
12
  import glob
13
+ import subprocess
13
14
 
14
- import thingy.process as process
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
- try:
40
- sourcefiles += process.run(['rgrep', '-E', '--exclude-dir=.git', '-l', '#![[:space:]]*/usr/bin/(env[[:space:]])?python3'] + args.paths)
41
- except process.RunError as exc:
42
- if exc.status == 1:
43
- sys.stderr.write('No Python3 source files found\n')
44
- sys.exit(2)
45
- else:
46
- sys.stderr.write('%d: %s\n' % (exc.status, exc.msg))
47
- sys.exit(1)
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 "%s"' % entry)
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
- process.run(['pylint3', '--output-format', 'parseable'] + sourcefiles, foreground=True)
63
- except process.RunError as exc:
64
- status = exc.status
65
- else:
66
- status = 0
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 status >= 64:
69
- sys.stderr.write('Unexpected error: %d\n' % status)
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 status
81
+ return result.returncode
74
82
 
75
83
  ################################################################################
76
84
 
skilleter_thingy/tfm.py CHANGED
@@ -82,8 +82,6 @@ import os
82
82
  import sys
83
83
  import argparse
84
84
  import curses
85
- import curses.panel
86
- import curses.textpad
87
85
  import subprocess
88
86
  import shutil
89
87
  from collections import defaultdict
@@ -12,7 +12,7 @@
12
12
  """
13
13
  ################################################################################
14
14
 
15
- import thingy.process as process
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 process.run(cmd):
36
+ for result in run.run(cmd):
37
37
  instances_list.append(result)
38
- except process.RunError as exc:
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
- process.run(['docker', 'stop', instance], foreground=True)
52
- except process.RunError as exc:
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
- process.run(cmd, foreground=True)
69
- except process.RunError as exc:
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 process.run(['docker', 'images', '-q']):
78
+ for result in run.run(['docker', 'images', '-q']):
79
79
  yield result
80
- except process.RunError as exc:
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
- process.run(cmd, foreground=True)
96
- except process.RunError as exc:
95
+ run.run(cmd, foreground=True)
96
+ except run.RunError as exc:
97
97
  raise DockerError(exc)
@@ -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 IOError('Unable to access %s' % filename)
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 = process.run(cmd)
44
- return result[0] if result else ""
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('git-ca is: %s' % file_type('git-ca', mimeflag))
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):
@@ -948,18 +948,21 @@ def ref(fields=('objectname'), sort=None, remotes=False, path=None):
948
948
 
949
949
  ################################################################################
950
950
 
951
- def branches(all=False, path=None):
951
+ def branches(all=False, path=None, remote=False):
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')
958
958
 
959
+ if remote:
960
+ cmd.append('--remote')
961
+
959
962
  results = []
960
963
  for output in git(cmd, path=path):
961
964
  if ' -> ' not in output and '(HEAD detached at ' not in output:
962
- results.append(output[2:])
965
+ results.append(output)
963
966
 
964
967
  return results
965
968
 
@@ -1250,7 +1253,10 @@ def matching_branch(branchname, case=False, path=None):
1250
1253
  otherwise, it just checks for a branches containing the branchname
1251
1254
  as a substring. """
1252
1255
 
1253
- all_branches = branches(all=True, path=path)
1256
+ local_branches = branches(path=path)
1257
+ remote_branches = branches(path=path, remote=True)
1258
+
1259
+ all_branches = local_branches + remote_branches
1254
1260
 
1255
1261
  # Always return exact matches
1256
1262
 
@@ -1284,8 +1290,8 @@ def matching_branch(branchname, case=False, path=None):
1284
1290
  # If the match is a remote branch, ignore it if we already have the equivalent
1285
1291
  # local branch, otherwise add the name of the local branch that would be created.
1286
1292
 
1287
- if branch.startswith('remotes/'):
1288
- localbranch = '/'.join(branch.split('/')[2:])
1293
+ if branch in remote_branches:
1294
+ localbranch = '/'.join(branch.split('/')[1:])
1289
1295
  if localbranch not in matching:
1290
1296
  matching_remote.append(localbranch)
1291
1297
  else:
@@ -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, exc_type, exc_value, exc_traceback):
75
+ def __exit__(self, _exc_type, _exc_value, _exc_traceback):
75
76
  """ Remove the popup """
76
77
 
77
78
  if self.panel:
@@ -37,108 +37,125 @@ class RunError(Exception):
37
37
 
38
38
  ################################################################################
39
39
 
40
- def capture_output(cmd, input_stream, output_streams, ansi_clean):
41
- """ Capture data from a stream (input_stream), optionally
42
- outputting it (if output_streams is not None and optionally
43
- saving it into a variable (data, if not None), terminating
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
- while True:
51
- output = input_stream.readline()
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
- if output:
54
- if output_streams:
55
- for stream in output_streams:
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
- elif cmd.poll() is not None:
67
- return
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 capture_continuous(cmd, input_stream, output_streams, ansi_clean):
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: ansi_clean not implemented
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.read(1)
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
- if output == '\n':
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.write(output)
97
- stream.flush()
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 process(command,
108
- stdout=None, stderr=None,
109
- return_stdout=False, return_stderr=False,
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 exception is True and the
141
- command returned a non-zero status code. """
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 unless asked not to
185
+ # Capture stdout/stderr to arrays
169
186
 
170
187
  stdout_data = []
171
188
  stderr_data = []
172
189
 
173
- if return_stdout:
174
- stdout.append(stdout_data)
175
-
176
- if return_stderr:
177
- stderr.append(stderr_data)
190
+ stdout.append(stdout_data)
191
+ stderr.append(stderr_data)
178
192
 
179
- # If running via the shell then the command should be a string, otherwise
180
- # it should be an array
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=capture_continuous if continuous else capture_output, args=(cmd, cmd.stdout, stdout, ansi_clean), daemon=True)
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=capture_continuous if continuous else capture_output, args=(cmd, cmd.stderr, stderr, ansi_clean), daemon=True)
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 (if required)
242
+ # If the command failed, raise an exception
237
243
 
238
- if exception and cmd.returncode:
239
- if return_stderr:
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, stderr=None,
252
- shell=False,
253
- output=None,
254
- ansi_clean=False,
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
- by default if it is non-zero """
262
+ if it is non-zero """
263
263
 
264
- result = process(command=command,
265
- stdout=stdout, stderr=stderr,
266
- return_stdout=True, return_stderr=True,
267
- shell=shell,
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 = process(cmd,
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, shell=True)
362
- test_run('false', exception=False)
363
- test_run('true', stdout=sys.stdout, exception=False)
364
- test_run(['git', 'status'], stdout=sys.stdout, stderr=sys.stderr, exception=False)
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, exception=False)
367
- test_run(['make'], stdout=sys.stdout, stderr=[sys.stderr], exception=False)
368
- test_run(['make'], stdout=True, exception=False)
369
- test_run(['make'], stdout=sys.stdout, exception=False)
370
- test_run(['make'], exception=False)
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, shell=True, return_stdout=False)
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
@@ -0,0 +1,3 @@
1
+ import subprocess
2
+
3
+ subprocess.check_call(['ls'])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skilleter_thingy
3
- Version: 0.2.13
3
+ Version: 0.2.15
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=rEgotUaMj9JxDCwz-7H5vdqxH_bXllaHqttwsOUGKj8,19235
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=22wrUNzd4BH3gi2ZL_rdw1vrPUNkJFb0pFFu_tuzhKs,10542
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=Z_e0wyQJ2AzOSy5cPI3jmAWystpJbHH4ygENjFagepo,52576
18
- skilleter_thingy/git_update.py,sha256=Z3p3E33ZJ2DVa107UzaFR9V8LXADRuHjJL8TX3IhEa4,14348
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=MlRimPj-S-ov44ETUvocRvfygs3cArP6WdSJCIJWNCs,11347
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=uAQRvscsW5x85P4COXE6HWk4XykBkRmGowAdViDkwi4,2601
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=xMsqcuNJ32PwKF5vO3SO6etlbJKbCLUJhSdC2w0clwE,33829
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=zFgLLGAmqGsBdy9SWhl6BVkPyng9vZygPe7dJylkTEc,2533
44
- skilleter_thingy/thingy/files.py,sha256=jNnLpkhfmZ3W2uqdQvt-zI27FSwBLBERwAbsVeQ2TTs,4708
45
- skilleter_thingy/thingy/git.py,sha256=NTZv9um6-Yigqdt-O9WTNxA19LyNTXw9IunrdflEpoM,43036
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=aa4vFwXiZ9JkH-zJuKZWACw4Ympcomi6EsPdlLIJiSs,43227
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=hNfA9yh4jCv2su8XK33udcTWwgf98noBdYRRkFX1mxc,2517
49
- skilleter_thingy/thingy/process.py,sha256=R8JXKaxJJ6rgnSdJYJIsJ7Ox2e-5nPV1EJ0sjGZGxec,1092
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.13.dist-info/licenses/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
55
- skilleter_thingy-0.2.13.dist-info/METADATA,sha256=p-NDmQvDF8A6oO5s1Lx1VIKmApg1e2f1VfyJsKqfTg8,28914
56
- skilleter_thingy-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
- skilleter_thingy-0.2.13.dist-info/entry_points.txt,sha256=MTNWf8jOx8Fy3tSwVLCZPlEyzlDF36odw-IN-cSefP8,1784
58
- skilleter_thingy-0.2.13.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
59
- skilleter_thingy-0.2.13.dist-info/RECORD,,
54
+ skilleter_thingy-0.2.15.dist-info/licenses/LICENSE,sha256=ljOS4DjXvqEo5VzGfdaRwgRZPbNScGBmfwyC8PChvmQ,32422
55
+ skilleter_thingy-0.2.15.dist-info/METADATA,sha256=NV9puo96TLDp25-yZ2LNqq95Q3eAAIcNX0rhciQJh58,28914
56
+ skilleter_thingy-0.2.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
57
+ skilleter_thingy-0.2.15.dist-info/entry_points.txt,sha256=MTNWf8jOx8Fy3tSwVLCZPlEyzlDF36odw-IN-cSefP8,1784
58
+ skilleter_thingy-0.2.15.dist-info/top_level.txt,sha256=8-JhgToBBiWURunmvfpSxEvNkDHQQ7r25-aBXtZv61g,17
59
+ skilleter_thingy-0.2.15.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']