skilleter-thingy 0.0.40__py3-none-any.whl → 0.0.42__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.

Files changed (68) hide show
  1. skilleter_thingy/__init__.py +6 -0
  2. skilleter_thingy/addpath.py +107 -0
  3. skilleter_thingy/borger.py +269 -0
  4. skilleter_thingy/console_colours.py +63 -0
  5. skilleter_thingy/diskspacecheck.py +67 -0
  6. skilleter_thingy/docker_purge.py +113 -0
  7. skilleter_thingy/ffind.py +536 -0
  8. skilleter_thingy/ggit.py +90 -0
  9. skilleter_thingy/ggrep.py +154 -0
  10. skilleter_thingy/git_br.py +180 -0
  11. skilleter_thingy/git_ca.py +142 -0
  12. skilleter_thingy/git_cleanup.py +287 -0
  13. skilleter_thingy/git_co.py +220 -0
  14. skilleter_thingy/git_common.py +61 -0
  15. skilleter_thingy/git_hold.py +154 -0
  16. skilleter_thingy/git_mr.py +92 -0
  17. skilleter_thingy/git_parent.py +77 -0
  18. skilleter_thingy/git_review.py +1428 -0
  19. skilleter_thingy/git_update.py +385 -0
  20. skilleter_thingy/git_wt.py +96 -0
  21. skilleter_thingy/gitcmp_helper.py +322 -0
  22. skilleter_thingy/gitprompt.py +274 -0
  23. skilleter_thingy/gl.py +174 -0
  24. skilleter_thingy/gphotosync.py +610 -0
  25. skilleter_thingy/linecount.py +155 -0
  26. skilleter_thingy/moviemover.py +133 -0
  27. skilleter_thingy/photodupe.py +136 -0
  28. skilleter_thingy/phototidier.py +248 -0
  29. skilleter_thingy/py_audit.py +131 -0
  30. skilleter_thingy/readable.py +270 -0
  31. skilleter_thingy/remdir.py +126 -0
  32. skilleter_thingy/rmdupe.py +550 -0
  33. skilleter_thingy/rpylint.py +91 -0
  34. skilleter_thingy/splitpics.py +99 -0
  35. skilleter_thingy/strreplace.py +82 -0
  36. skilleter_thingy/sysmon.py +435 -0
  37. skilleter_thingy/tfm.py +920 -0
  38. skilleter_thingy/tfparse.py +101 -0
  39. skilleter_thingy/thingy/__init__.py +6 -0
  40. skilleter_thingy/thingy/colour.py +213 -0
  41. skilleter_thingy/thingy/dc_curses.py +278 -0
  42. skilleter_thingy/thingy/dc_defaults.py +221 -0
  43. skilleter_thingy/thingy/dc_util.py +50 -0
  44. skilleter_thingy/thingy/dircolors.py +308 -0
  45. skilleter_thingy/thingy/docker.py +95 -0
  46. skilleter_thingy/thingy/files.py +142 -0
  47. skilleter_thingy/thingy/git.py +1371 -0
  48. skilleter_thingy/thingy/git2.py +1307 -0
  49. skilleter_thingy/thingy/gitlab.py +193 -0
  50. skilleter_thingy/thingy/logger.py +112 -0
  51. skilleter_thingy/thingy/path.py +156 -0
  52. skilleter_thingy/thingy/popup.py +87 -0
  53. skilleter_thingy/thingy/process.py +112 -0
  54. skilleter_thingy/thingy/run.py +334 -0
  55. skilleter_thingy/thingy/tfm_pane.py +595 -0
  56. skilleter_thingy/thingy/tidy.py +160 -0
  57. skilleter_thingy/trimpath.py +84 -0
  58. skilleter_thingy/window_rename.py +92 -0
  59. skilleter_thingy/xchmod.py +125 -0
  60. skilleter_thingy/yamlcheck.py +89 -0
  61. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/METADATA +5 -1
  62. skilleter_thingy-0.0.42.dist-info/RECORD +66 -0
  63. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/entry_points.txt +1 -0
  64. skilleter_thingy-0.0.42.dist-info/top_level.txt +1 -0
  65. skilleter_thingy-0.0.40.dist-info/RECORD +0 -6
  66. skilleter_thingy-0.0.40.dist-info/top_level.txt +0 -1
  67. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/LICENSE +0 -0
  68. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.42.dist-info}/WHEEL +0 -0
@@ -0,0 +1,6 @@
1
+ import os
2
+ import sys
3
+
4
+ thingy_path = os.path.dirname(os.path.realpath(__file__))
5
+
6
+ sys.path.append(thingy_path)
@@ -0,0 +1,107 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Thingy addpath command
5
+
6
+ Copyright (C) 2018 John Skilleter
7
+
8
+ Update a $PATH-type variable by adding or removing entries.
9
+
10
+ Intended to be used as in:
11
+
12
+ export PATH=$(addpath $PATH --add /opt/bin)
13
+ """
14
+ ################################################################################
15
+
16
+ import sys
17
+ import os
18
+ import argparse
19
+
20
+ ################################################################################
21
+
22
+ def pathmod(pathentries, separator, pathlist, prefix=False, suffix=False, delete=False, force=False):
23
+ """ Modify a path.
24
+ """
25
+
26
+ # Only do something if the list of paths to add exists
27
+
28
+ if pathlist:
29
+ # Join the list path entries together then split them into individual entries
30
+ # Allows for a list of entries in the form ['a:b:c', 'd', 'e']
31
+
32
+ paths = separator.join(pathlist).split(separator)
33
+
34
+ # Process each entry
35
+
36
+ for entry in paths:
37
+ # Do nothing (except delete) if the path does not exist and we aren't forcing
38
+
39
+ if not entry or (not os.path.isdir(entry) and not (force or delete)):
40
+ continue
41
+
42
+ # If we are removing or adding/moving an entry remove any existing entry
43
+
44
+ if (delete or prefix or suffix) and entry in pathentries:
45
+ pathentries.remove(entry)
46
+
47
+ # Prefix or suffix the entry
48
+
49
+ if not delete and entry not in pathentries:
50
+ if suffix:
51
+ pathentries.append(entry)
52
+ else:
53
+ pathentries.insert(0, entry)
54
+
55
+ return pathentries
56
+
57
+ ################################################################################
58
+
59
+ def main():
60
+ """ Main function - handles command line, outputs result to stdout """
61
+
62
+ parser = argparse.ArgumentParser(description='Add or remove entries from a path list (e.g. as used by the PATH environment variable)')
63
+ parser.add_argument('--add', action='append', help='Add an entry to the front of the path (do nothing if it is already present in the path)')
64
+ parser.add_argument('--prefix', action='append', help='Add an entry to the front of the path (or move it there if it is already present)')
65
+ parser.add_argument('--suffix', action='append', help='Add an entry to the end of the path (or move it there if it is already present)')
66
+ parser.add_argument('--remove', action='append', help='Remove an entry from the path (do nothing if it is not present')
67
+ parser.add_argument('--force', default=False, help='Add entries even if a corresponding directory does not exist')
68
+ parser.add_argument('--separator', action='store', default=':', help='Override the default path separator')
69
+ parser.add_argument('path', nargs=1, help='The path to modify')
70
+
71
+ args = parser.parse_args()
72
+
73
+ # Split the given path into component parts
74
+
75
+ pathsplit = [pathentry for pathentry in args.path[0].split(args.separator) if pathentry]
76
+
77
+ # Process the additions, suffixations, prefixanisms and deletes.
78
+
79
+ pathmod(pathsplit, args.separator, args.add)
80
+ pathmod(pathsplit, args.separator, args.prefix, prefix=True)
81
+ pathmod(pathsplit, args.separator, args.suffix, suffix=True)
82
+ pathmod(pathsplit, args.separator, args.remove, delete=True)
83
+
84
+ # Glue the path back together
85
+
86
+ pathjoin = args.separator.join(pathsplit)
87
+
88
+ # Output the updated path to stdout
89
+
90
+ print(pathjoin)
91
+
92
+ ################################################################################
93
+
94
+ def addpath():
95
+ """Entry point"""
96
+
97
+ try:
98
+ main()
99
+ except KeyboardInterrupt:
100
+ sys.exit(1)
101
+ except BrokenPipeError:
102
+ sys.exit(2)
103
+
104
+ ################################################################################
105
+
106
+ if __name__ == '__main__':
107
+ addpath()
@@ -0,0 +1,269 @@
1
+ #! /usr/bin/env python3
2
+
3
+ """
4
+ Wrapper for the borg backup command
5
+
6
+ Copyright (C) 2018 John Skilleter
7
+
8
+ TODO: Major tidy-up as this is a translation of a Bash script.
9
+ TODO: Merge with the usb-backup script since both do almost the same job
10
+ TODO: Default configuration file should be named for the hostname
11
+ TODO: Move all configuration data into the configuration file
12
+ """
13
+
14
+ ################################################################################
15
+ # Imports
16
+
17
+ import sys
18
+ import os
19
+ import time
20
+ import argparse
21
+ import configparser
22
+ import subprocess
23
+ from pathlib import Path
24
+
25
+ ################################################################################
26
+ # Variables
27
+
28
+ DEFAULT_CONFIG_FILE = Path('borger.ini')
29
+
30
+ COMMANDS = ('backup', 'mount', 'umount', 'compact', 'info', 'prune', 'check', 'init')
31
+
32
+ # TODO: NOT USED
33
+ PRUNE_OPTIONS = [
34
+ '--keep-within', '7d',
35
+ '--keep-daily', '30',
36
+ '--keep-weekly', '26',
37
+ '--keep-monthly', '24',
38
+ '--keep-yearly', '10',
39
+ ]
40
+
41
+ ################################################################################
42
+
43
+ def run(args, cmd):
44
+ """Run a subprocess."""
45
+
46
+ if args.debug:
47
+ cmd_str = ' '.join(cmd)
48
+ print(f'Running "{cmd_str}"')
49
+
50
+ return subprocess.run(cmd, check=True)
51
+
52
+ ################################################################################
53
+
54
+ def borg_backup(args, exclude_list):
55
+ """Perform a backup."""
56
+
57
+ create_options = ['--compression', 'auto,lzma']
58
+
59
+ version = time.strftime('%Y-%m-%d-%H:%M:%S')
60
+
61
+ print(f'Creating backup version {version}')
62
+
63
+ if args.verbose:
64
+ create_options += ['--list', '--filter=AMC']
65
+
66
+ if args.dryrun:
67
+ create_options.append('--dry-run')
68
+ else:
69
+ create_options.append('--stats')
70
+
71
+ exclude_opts = []
72
+
73
+ if exclude_list:
74
+ for exclude in exclude_list:
75
+ exclude_opts += ['--exclude', exclude]
76
+
77
+ os.chdir(args.source)
78
+
79
+ run(args,
80
+ ['borg'] + args.options + ['create', f'{str(args.destination)}::{version}', str(args.source)] + create_options +
81
+ ['--show-rc', '--one-file-system', '--exclude-caches'] + exclude_opts)
82
+
83
+ ################################################################################
84
+
85
+ def borg_prune(args):
86
+ """Prune the repo by limiting the number of backups stored."""
87
+
88
+ print('Pruning old backups')
89
+
90
+ # Keep all backups for at least 7 days, 1 per day for 30 days, 1 per week for 2 years
91
+ # 1 per month for 4 years and 1 per year for 10 years.
92
+
93
+ run(args, ['borg'] + args.options + ['prune', str(args.destination)] + PRUNE_OPTIONS)
94
+
95
+ ################################################################################
96
+
97
+ def borg_compact(args):
98
+ """Compact the repo."""
99
+
100
+ print('Compacting the backup')
101
+
102
+ # Keep all backups for at least 7 days, 1 per day for 30 days, 1 per week for 2 years
103
+ # 1 per month for 4 years and 1 per year for 10 years.
104
+
105
+ run(args, ['borg'] + args.options + ['compact', str(args.destination)])
106
+
107
+ ################################################################################
108
+
109
+ def borg_info(args):
110
+ """Info."""
111
+
112
+ run(args, ['borg'] + args.options + ['info', str(args.destination)])
113
+
114
+ ################################################################################
115
+
116
+ def borg_mount(args):
117
+ """Mount."""
118
+
119
+ print(f'Mounting Borg backups at {args.mount_dir}')
120
+
121
+ mount = Path(args.mount_dir)
122
+
123
+ if not mount.is_dir():
124
+ mount.mkdir()
125
+
126
+ run(args, ['borg'] + args.options + ['mount', str(args.destination), str(mount)])
127
+
128
+ ################################################################################
129
+
130
+ def borg_umount(args):
131
+ """Unmount."""
132
+
133
+ print('Unmounting {args.mount}')
134
+
135
+ run(args, ['borg'] + args.options + ['umount', str(args.mount)])
136
+
137
+ ################################################################################
138
+
139
+ def borg_check(args):
140
+ """Check the status of a backup."""
141
+
142
+ run(args, ['borg'] + args.options + ['check', str(args.destination)])
143
+
144
+ ################################################################################
145
+
146
+ def borg_init(args):
147
+ """Initialise a backup."""
148
+
149
+ run(args, ['borg'] + args.options + ['init', str(args.destination), '--encryption=none'])
150
+
151
+ ################################################################################
152
+
153
+ def process_excludes(exclude_data):
154
+ """Process the include list from the configuration file."""
155
+
156
+ return exclude_data.replace('%', str(Path.cwd())).split('\n')
157
+
158
+ ################################################################################
159
+
160
+ def main():
161
+ """Entry point."""
162
+
163
+ command_list = ', '.join(COMMANDS)
164
+
165
+ parser = argparse.ArgumentParser(description='Wrapper app for Borg backup to make it easier to use')
166
+ parser.add_argument('--dryrun', '--dry-run', '-D', action='store_true', help='Dry-run comands')
167
+ parser.add_argument('--debug', '-d', action='store_true', help='Debug')
168
+ parser.add_argument('--verbose', '-v', action='store_true', help='Verbosity to the maximum')
169
+ parser.add_argument('--config', '-c', default=None, help='Specify the configuration file')
170
+ parser.add_argument('commands', nargs='+', help=f'One or more commands ({command_list})')
171
+ args = parser.parse_args()
172
+
173
+ # If no config file specified then look in all the usual places
174
+
175
+ if args.config:
176
+ args.config = Path(args.config)
177
+ elif DEFAULT_CONFIG_FILE.is_file():
178
+ args.config = DEFAULT_CONFIG_FILE
179
+ else:
180
+ args.config = Path.home() / DEFAULT_CONFIG_FILE
181
+
182
+ if not args.config.is_file():
183
+ args.config = Path(sys.argv[0]).parent / DEFAULT_CONFIG_FILE
184
+
185
+ # Check that the configuration file exists
186
+
187
+ if not args.config.is_file():
188
+ print(f'Configuration file "{args.config}" not found')
189
+ sys.exit(1)
190
+
191
+ # Default options
192
+
193
+ args.options = []
194
+
195
+ # Read the configuration file
196
+
197
+ config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
198
+ config.read(args.config)
199
+
200
+ if 'borger' not in config:
201
+ print('Invalid configuration file "args.config"')
202
+ sys.exit(1)
203
+
204
+ exclude = process_excludes(config['borger']['exclude']) if 'exclude' in config['borger'] else []
205
+
206
+ if 'prune' in config['borger']:
207
+ # TODO: Stuff
208
+ print('Parser for the prune option is not implemented yet')
209
+ sys.exit(1)
210
+
211
+ if 'destination' in config['borger']:
212
+ args.destination = config['borger']['destination']
213
+ else:
214
+ print('Destination directory not specified')
215
+ sys.exit(1)
216
+
217
+ if 'source' in config['borger']:
218
+ args.source = Path(config['borger']['source'])
219
+ else:
220
+ print('Source directory not specified')
221
+ sys.exit(1)
222
+
223
+ # Initialise if necessary
224
+
225
+ if args.debug:
226
+ args.options.append('--verbose')
227
+
228
+ if args.verbose:
229
+ args.options.append('--progress')
230
+
231
+ # Decide what to do
232
+
233
+ for command in args.commands:
234
+ if command == 'backup':
235
+ borg_backup(args, exclude)
236
+ elif command == 'mount':
237
+ borg_mount(args)
238
+ elif command == 'umount':
239
+ borg_umount(args)
240
+ elif command == 'info':
241
+ borg_info(args)
242
+ elif command == 'prune':
243
+ borg_prune(args)
244
+ elif command == 'check':
245
+ borg_check(args)
246
+ elif command == 'init':
247
+ borg_init(args)
248
+ elif command == 'compact':
249
+ borg_compact(args)
250
+ else:
251
+ print(f'Unrecognized command: {command}')
252
+ sys.exit(2)
253
+
254
+ ################################################################################
255
+
256
+ def borger():
257
+ """Entry point"""
258
+
259
+ try:
260
+ main()
261
+ except KeyboardInterrupt:
262
+ sys.exit(1)
263
+ except BrokenPipeError:
264
+ sys.exit(2)
265
+
266
+ ################################################################################
267
+
268
+ if __name__ == '__main__':
269
+ borger()
@@ -0,0 +1,63 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Output all console colours
5
+
6
+ Copyright (C) 2017-18 John Skilleter
7
+
8
+ Licence: GPL v3 or later
9
+ """
10
+ ################################################################################
11
+
12
+ import sys
13
+
14
+ import thingy.colour as colour
15
+
16
+ ################################################################################
17
+
18
+ def main():
19
+ """ Main function - draw the colour grid """
20
+
21
+ # Extended ANSI colour are slightly weird.
22
+ # Colours 0-15 are the standard basic colours
23
+ # Colours 16-231 form a 6x6x6 colour cube
24
+ # Colours 232-255 are greyscale range with colours 0 and 15 as black and white
25
+
26
+ for code in range(0, 256):
27
+ if code in (8, 16) or (code > 16 and (code - 16) % 6 == 0):
28
+ colour.write('')
29
+
30
+ if (code - 16) % 36 == 0:
31
+ colour.write('')
32
+
33
+ # Set the foreground code to be white for dark backgrounds
34
+
35
+ foreground = 15 if code in (0, 1, 4, 5, 8, 12) \
36
+ or (16 <= code <= 33) \
37
+ or (52 <= code <= 69) \
38
+ or (88 <= code <= 105) \
39
+ or (124 <= code <= 135) \
40
+ or (160 <= code <= 171) \
41
+ or (196 <= code <= 201) \
42
+ or (232 <= code <= 243) else 0
43
+
44
+ colour.write('[B%d][%d] %3d [NORMAL] ' % (code, foreground, code), newline=False)
45
+
46
+ print()
47
+
48
+ ################################################################################
49
+
50
+ def console_colours():
51
+ """Entry point"""
52
+
53
+ try:
54
+ main()
55
+ except KeyboardInterrupt:
56
+ sys.exit(1)
57
+ except BrokenPipeError:
58
+ sys.exit(2)
59
+
60
+ ################################################################################
61
+
62
+ if __name__ == '__main__':
63
+ console_colours()
@@ -0,0 +1,67 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Check how much free space is available on all filesystems, ignoring
5
+ read-only filesystems, /dev and tmpfs.
6
+
7
+ Issue a warning if any are above 90% used.
8
+ """
9
+ ################################################################################
10
+
11
+ import sys
12
+ import argparse
13
+ import psutil
14
+
15
+ ################################################################################
16
+
17
+ WARNING_LEVEL = 15
18
+
19
+ ################################################################################
20
+
21
+ def main():
22
+ """ Do everything """
23
+
24
+ parser = argparse.ArgumentParser(description='Check for filesystems that are running low on space')
25
+ parser.add_argument('--level', action='store', type=int, default=WARNING_LEVEL,
26
+ help='Warning if less than this amount of space is available on any writeable, mounted filesystem (default=%d)' % WARNING_LEVEL)
27
+ args = parser.parse_args()
28
+
29
+ if args.level < 0 or args.level > 100:
30
+ print('Invalid value: %d' % args.level)
31
+ sys.exit(3)
32
+
33
+ disks = psutil.disk_partitions()
34
+ devices = []
35
+ warning = []
36
+
37
+ for disk in disks:
38
+ if 'ro' not in disk.opts.split(',') and disk.device not in devices:
39
+ devices.append(disk.device)
40
+ usage = psutil.disk_usage(disk.mountpoint)
41
+
42
+ disk_space = 100 - usage.percent
43
+
44
+ if disk_space < args.level:
45
+ warning.append('%s has only %2.1f%% space available' % (disk.mountpoint, disk_space))
46
+
47
+ if warning:
48
+ print('Filesystems with less than %d%% available space:' % args.level)
49
+ print('\n'.join(warning))
50
+
51
+ ################################################################################
52
+
53
+ def diskspacecheck():
54
+ """Entry point"""
55
+
56
+ try:
57
+ main()
58
+
59
+ except KeyboardInterrupt:
60
+ sys.exit(1)
61
+ except BrokenPipeError:
62
+ sys.exit(2)
63
+
64
+ ################################################################################
65
+
66
+ if __name__ == '__main__':
67
+ diskspacecheck()
@@ -0,0 +1,113 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Thingy docker-purge command
5
+
6
+ Copyright (C) 2017 John Skilleter
7
+
8
+ Initial version - contains only basic error checking and limited debug output.
9
+ """
10
+ ################################################################################
11
+
12
+ import sys
13
+ import re
14
+ import argparse
15
+
16
+ import thingy.logger as logger
17
+ import thingy.docker as docker
18
+
19
+ ################################################################################
20
+
21
+ def initialise():
22
+ """ Parse the command line """
23
+
24
+ parser = argparse.ArgumentParser(description='Purge docker instances and images')
25
+
26
+ parser.add_argument('-s', '--stop', action='store_true', help='Stop Docker instances')
27
+ parser.add_argument('-k', '--kill', action='store_true', help='Kill Docker instances')
28
+ parser.add_argument('-r', '--remove', action='store_true', help='Remove Docker images')
29
+ parser.add_argument('-l', '--list', action='store_true', help='List what would be done without doing it')
30
+ parser.add_argument('-f', '--force', action='store_true', help='Forcibly kill/remove instances')
31
+ parser.add_argument('--debug', action='store_true', help='Enable debug output')
32
+ parser.add_argument('images', nargs='*', help='List of Docker containers (regular expression)')
33
+
34
+ args = parser.parse_args()
35
+
36
+ # Configure logging
37
+
38
+ log = logger.init('git-prompt')
39
+
40
+ if args.debug:
41
+ log.setLevel(logger.DEBUG)
42
+ log.info('Debug logging enabled')
43
+
44
+ # Default is to stop matching images
45
+
46
+ if not args.stop and not args.kill and not args.remove:
47
+ args.stop = True
48
+
49
+ # Default is to match all containers
50
+
51
+ if not args.images:
52
+ args.images = '.*'
53
+ else:
54
+ args.images = '|'.join(args.images)
55
+
56
+ log.info('Arguments: %s', args)
57
+ return args
58
+
59
+ ################################################################################
60
+
61
+ def main(args):
62
+ """ Main code """
63
+
64
+ try:
65
+ if args.stop or args.kill:
66
+ for instance in docker.instances():
67
+ if re.match(args.images, instance):
68
+
69
+ print('Stopping instance: %s' % instance)
70
+
71
+ if not args.list:
72
+ docker.stop(instance, force=args.force)
73
+
74
+ if args.kill:
75
+ for instance in docker.instances(all=True):
76
+ if re.match(args.images, instance):
77
+
78
+ print('Removing instance: %s' % instance)
79
+
80
+ if not args.list:
81
+ docker.rm(instance)
82
+
83
+ if args.remove:
84
+ for image in docker.images():
85
+ if re.match(args.images, image):
86
+
87
+ print('Removing image: %s' % image)
88
+
89
+ if not args.list:
90
+ docker.rmi(image, force=args.force)
91
+
92
+ except docker.DockerError as exc:
93
+ sys.stderr.write(str(exc))
94
+ sys.exit(1)
95
+
96
+ ################################################################################
97
+
98
+ def docker_purge():
99
+ """Entry point"""
100
+
101
+ try:
102
+ config = initialise()
103
+ main(config)
104
+
105
+ except KeyboardInterrupt:
106
+ sys.exit(1)
107
+ except BrokenPipeError:
108
+ sys.exit(2)
109
+
110
+ ################################################################################
111
+
112
+ if __name__ == '__main__':
113
+ docker_purge()