skilleter-thingy 0.0.22__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 (67) hide show
  1. skilleter_thingy/__init__.py +0 -0
  2. skilleter_thingy/addpath.py +107 -0
  3. skilleter_thingy/aws.py +548 -0
  4. skilleter_thingy/borger.py +269 -0
  5. skilleter_thingy/colour.py +213 -0
  6. skilleter_thingy/console_colours.py +63 -0
  7. skilleter_thingy/dc_curses.py +278 -0
  8. skilleter_thingy/dc_defaults.py +221 -0
  9. skilleter_thingy/dc_util.py +50 -0
  10. skilleter_thingy/dircolors.py +308 -0
  11. skilleter_thingy/diskspacecheck.py +67 -0
  12. skilleter_thingy/docker.py +95 -0
  13. skilleter_thingy/docker_purge.py +113 -0
  14. skilleter_thingy/ffind.py +536 -0
  15. skilleter_thingy/files.py +142 -0
  16. skilleter_thingy/ggit.py +90 -0
  17. skilleter_thingy/ggrep.py +154 -0
  18. skilleter_thingy/git.py +1368 -0
  19. skilleter_thingy/git2.py +1307 -0
  20. skilleter_thingy/git_br.py +180 -0
  21. skilleter_thingy/git_ca.py +142 -0
  22. skilleter_thingy/git_cleanup.py +287 -0
  23. skilleter_thingy/git_co.py +220 -0
  24. skilleter_thingy/git_common.py +61 -0
  25. skilleter_thingy/git_hold.py +154 -0
  26. skilleter_thingy/git_mr.py +92 -0
  27. skilleter_thingy/git_parent.py +77 -0
  28. skilleter_thingy/git_review.py +1416 -0
  29. skilleter_thingy/git_update.py +385 -0
  30. skilleter_thingy/git_wt.py +96 -0
  31. skilleter_thingy/gitcmp_helper.py +322 -0
  32. skilleter_thingy/gitlab.py +193 -0
  33. skilleter_thingy/gitprompt.py +274 -0
  34. skilleter_thingy/gl.py +174 -0
  35. skilleter_thingy/gphotosync.py +610 -0
  36. skilleter_thingy/linecount.py +155 -0
  37. skilleter_thingy/logger.py +112 -0
  38. skilleter_thingy/moviemover.py +133 -0
  39. skilleter_thingy/path.py +156 -0
  40. skilleter_thingy/photodupe.py +110 -0
  41. skilleter_thingy/phototidier.py +248 -0
  42. skilleter_thingy/popup.py +87 -0
  43. skilleter_thingy/process.py +112 -0
  44. skilleter_thingy/py_audit.py +131 -0
  45. skilleter_thingy/readable.py +270 -0
  46. skilleter_thingy/remdir.py +126 -0
  47. skilleter_thingy/rmdupe.py +550 -0
  48. skilleter_thingy/rpylint.py +91 -0
  49. skilleter_thingy/run.py +334 -0
  50. skilleter_thingy/s3_sync.py +383 -0
  51. skilleter_thingy/splitpics.py +99 -0
  52. skilleter_thingy/strreplace.py +82 -0
  53. skilleter_thingy/sysmon.py +435 -0
  54. skilleter_thingy/tfm.py +920 -0
  55. skilleter_thingy/tfm_pane.py +595 -0
  56. skilleter_thingy/tfparse.py +101 -0
  57. skilleter_thingy/tidy.py +160 -0
  58. skilleter_thingy/trimpath.py +84 -0
  59. skilleter_thingy/window_rename.py +92 -0
  60. skilleter_thingy/xchmod.py +125 -0
  61. skilleter_thingy/yamlcheck.py +89 -0
  62. skilleter_thingy-0.0.22.dist-info/LICENSE +619 -0
  63. skilleter_thingy-0.0.22.dist-info/METADATA +22 -0
  64. skilleter_thingy-0.0.22.dist-info/RECORD +67 -0
  65. skilleter_thingy-0.0.22.dist-info/WHEEL +5 -0
  66. skilleter_thingy-0.0.22.dist-info/entry_points.txt +43 -0
  67. skilleter_thingy-0.0.22.dist-info/top_level.txt +1 -0
@@ -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,213 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Colour colour output
5
+
6
+ Copyright (C) 2017-18 John Skilleter
7
+
8
+ Licence: GPL v3 or later
9
+
10
+ 0-15 are the standard VGA colour codes
11
+ 16-21 are a few extras
12
+ 22-231 appear to be a sort of colour cube
13
+ 232-255 are 24 shades of grey
14
+ """
15
+ ################################################################################
16
+
17
+ import sys
18
+ import re
19
+
20
+ ################################################################################
21
+ # Constants
22
+
23
+ _ANSI_NORMAL = '\x1b[0m'
24
+ _ANSI_BOLD = '\x1b[1m'
25
+ _ANSI_UNDERSCORE = '\x1b[4m'
26
+ _ANSI_BLINK = '\x1b[5m'
27
+ _ANSI_REVERSE = '\x1b[7m'
28
+ _ANSI_BLACK = '\x1b[30m'
29
+ _ANSI_RED = '\x1b[31m'
30
+ _ANSI_GREEN = '\x1b[32m'
31
+ _ANSI_YELLOW = '\x1b[33m'
32
+ _ANSI_BLUE = '\x1b[34m'
33
+ _ANSI_MAGENTA = '\x1b[35m'
34
+ _ANSI_CYAN = '\x1b[36m'
35
+ _ANSI_WHITE = '\x1b[37m'
36
+ _ANSI_BBLACK = '\x1b[40m'
37
+ _ANSI_BRED = '\x1b[41m'
38
+ _ANSI_BGREEN = '\x1b[42m'
39
+ _ANSI_BYELLOW = '\x1b[43m'
40
+ _ANSI_BBLUE = '\x1b[44m'
41
+ _ANSI_BMAGENTA = '\x1b[45m'
42
+ _ANSI_BCYAN = '\x1b[46m'
43
+ _ANSI_BWHITE = '\x1b[47m'
44
+
45
+ # Looking up tables for converting textual colour codes to ANSI codes
46
+
47
+ ANSI_REGEXES = \
48
+ (
49
+ (r'\[NORMAL:(.*?)\]', r'%s\1%s' % (_ANSI_NORMAL, _ANSI_NORMAL)),
50
+ (r'\[BOLD:(.*?)\]', r'%s\1%s' % (_ANSI_BOLD, _ANSI_NORMAL)),
51
+ (r'\[UNDERSCORE:(.*?)\]', r'%s\1%s' % (_ANSI_UNDERSCORE, _ANSI_NORMAL)),
52
+ (r'\[BLINK:(.*?)\]', r'%s\1%s' % (_ANSI_BLINK, _ANSI_NORMAL)),
53
+ (r'\[REVERSE:(.*?)\]', r'%s\1%s' % (_ANSI_REVERSE, _ANSI_NORMAL)),
54
+
55
+ (r'\[BLACK:(.*?)\]', r'%s\1%s' % (_ANSI_BLACK, _ANSI_NORMAL)),
56
+ (r'\[RED:(.*?)\]', r'%s\1%s' % (_ANSI_RED, _ANSI_NORMAL)),
57
+ (r'\[GREEN:(.*?)\]', r'%s\1%s' % (_ANSI_GREEN, _ANSI_NORMAL)),
58
+ (r'\[YELLOW:(.*?)\]', r'%s\1%s' % (_ANSI_YELLOW, _ANSI_NORMAL)),
59
+ (r'\[BLUE:(.*?)\]', r'%s\1%s' % (_ANSI_BLUE, _ANSI_NORMAL)),
60
+ (r'\[MAGENTA:(.*?)\]', r'%s\1%s' % (_ANSI_MAGENTA, _ANSI_NORMAL)),
61
+ (r'\[CYAN:(.*?)\]', r'%s\1%s' % (_ANSI_CYAN, _ANSI_NORMAL)),
62
+ (r'\[WHITE:(.*?)\]', r'%s\1%s' % (_ANSI_WHITE, _ANSI_NORMAL)),
63
+
64
+ (r'\[BBLACK:(.*?)\]', r'%s\1%s' % (_ANSI_BBLACK, _ANSI_NORMAL)),
65
+ (r'\[BRED:(.*?)\]', r'%s\1%s' % (_ANSI_BRED, _ANSI_NORMAL)),
66
+ (r'\[BGREEN:(.*?)\]', r'%s\1%s' % (_ANSI_BGREEN, _ANSI_NORMAL)),
67
+ (r'\[BYELLOW:(.*?)\]', r'%s\1%s' % (_ANSI_BYELLOW, _ANSI_NORMAL)),
68
+ (r'\[BBLUE:(.*?)\]', r'%s\1%s' % (_ANSI_BBLUE, _ANSI_NORMAL)),
69
+ (r'\[BMAGENTA:(.*?)\]', r'%s\1%s' % (_ANSI_BMAGENTA, _ANSI_NORMAL)),
70
+ (r'\[BCYAN:(.*?)\]', r'%s\1%s' % (_ANSI_BCYAN, _ANSI_NORMAL)),
71
+ (r'\[BWHITE:(.*?)\]', r'%s\1%s' % (_ANSI_BWHITE, _ANSI_NORMAL))
72
+ )
73
+
74
+ ANSI_CODES = \
75
+ (
76
+ ('[NORMAL]', _ANSI_NORMAL),
77
+ ('[BOLD]', _ANSI_BOLD),
78
+ ('[UNDERSCORE]', _ANSI_UNDERSCORE),
79
+ ('[BLINK]', _ANSI_BLINK),
80
+ ('[REVERSE]', _ANSI_REVERSE),
81
+
82
+ ('[BLACK]', _ANSI_BLACK),
83
+ ('[RED]', _ANSI_RED),
84
+ ('[GREEN]', _ANSI_GREEN),
85
+ ('[YELLOW]', _ANSI_YELLOW),
86
+ ('[BLUE]', _ANSI_BLUE),
87
+ ('[MAGENTA]', _ANSI_MAGENTA),
88
+ ('[CYAN]', _ANSI_CYAN),
89
+ ('[WHITE]', _ANSI_WHITE),
90
+
91
+ ('[BBLACK]', _ANSI_BBLACK),
92
+ ('[BRED]', _ANSI_BRED),
93
+ ('[BGREEN]', _ANSI_BGREEN),
94
+ ('[BYELLOW]', _ANSI_BYELLOW),
95
+ ('[BBLUE]', _ANSI_BBLUE),
96
+ ('[BMAGENTA]', _ANSI_BMAGENTA),
97
+ ('[BCYAN]', _ANSI_BCYAN),
98
+ ('[BWHITE]', _ANSI_BWHITE),
99
+ )
100
+
101
+ # Regex to match an ANSI control sequence
102
+
103
+ RE_ANSI = re.compile(r'\x1b\[([0-9][0-9;]*)*m')
104
+
105
+ ################################################################################
106
+
107
+ def format(txt):
108
+ """ Convert textual colour codes in a string to ANSI codes.
109
+ Codes can be specified as either [COLOUR], where all following text
110
+ is output in the specified colour or [COLOR:text] where only 'text' is
111
+ output in the colour, with subsequent text output in the default colours """
112
+
113
+ # Replace [COLOUR:text] with COLOURtextNORMAL using regexes
114
+
115
+ if re.search(r'\[.*:.*\]', txt):
116
+ for regex in ANSI_REGEXES:
117
+ txt = re.sub(regex[0], regex[1], txt)
118
+
119
+ # Replace [COLOUR] with COLOUR
120
+
121
+ if re.search(r'\[.*\]', txt):
122
+ for code in ANSI_CODES:
123
+ txt = txt.replace(code[0], code[1])
124
+
125
+ # Now replace [N(N)(N)] with 256 colour colour code.
126
+
127
+ while True:
128
+ p = re.match(r'.*\[([0-9]{1,3})\].*', txt)
129
+ if p is None:
130
+ break
131
+
132
+ value = int(p.group(1))
133
+ txt = txt.replace('[%s]' % p.group(1), '\x1b[38;5;%dm' % value)
134
+
135
+ while True:
136
+ p = re.match(r'.*\[B([0-9]{1,3})\].*', txt)
137
+ if p is None:
138
+ break
139
+
140
+ value = int(p.group(1))
141
+ txt = txt.replace('[B%s]' % p.group(1), '\x1b[48;5;%dm' % value)
142
+
143
+ return txt
144
+
145
+ ################################################################################
146
+
147
+ def write(txt=None, newline=True, stream=sys.stdout, indent=0):
148
+ """ Write to the specified stream (defaulting to stdout), converting colour codes to ANSI
149
+ txt can be None, a string or a list of strings."""
150
+
151
+ if txt:
152
+ if isinstance(txt, str):
153
+ txt = txt.split('\n')
154
+
155
+ for n, line in enumerate(txt):
156
+ line = format(line)
157
+
158
+ if indent:
159
+ stream.write(' ' * indent)
160
+
161
+ stream.write(line)
162
+
163
+ if newline or n < len(txt) - 1:
164
+ stream.write('\n')
165
+ elif newline:
166
+ stream.write('\n')
167
+
168
+ ################################################################################
169
+
170
+ def error(txt, newline=True, stream=sys.stderr, status=1):
171
+ """ Write an error message to the specified stream (defaulting to
172
+ stderr) and exit with the specified status code (defaulting to 1) """
173
+
174
+ write(txt, newline, stream)
175
+
176
+ sys.exit(status)
177
+
178
+ ################################################################################
179
+
180
+ if __name__ == '__main__':
181
+ write('Foreground: [RED]red [GREEN]green [BLACK]black [NORMAL]normal')
182
+ write('Background: [BRED]red [BGREEN]green [BBLACK]black [NORMAL]normal')
183
+
184
+ write('Foreground: [BBLUE:blue] [RED:red] normal')
185
+
186
+ for combo in (0, 1, 2):
187
+ print()
188
+ if combo == 0:
189
+ print('Background colours')
190
+ elif combo == 1:
191
+ print('Foreground colours')
192
+ else:
193
+ print('Combinations')
194
+
195
+ print()
196
+ for y in range(0, 32):
197
+ for x in range(0, 8):
198
+ colour = x + y * 8
199
+
200
+ if combo == 0:
201
+ write(format('[B%d] %04d ' % (colour, colour)), newline=False)
202
+ elif combo == 1:
203
+ write(format('[%d] %04d ' % (colour, colour)), newline=False)
204
+ else:
205
+ write(format('[B%d] %04d [%d] %04d ' % (colour, colour, 255 - colour, 255 - colour)), newline=False)
206
+
207
+ write('[NORMAL]')
208
+
209
+ print()
210
+
211
+ error('Error message (nothing should be output after this)', status=0)
212
+
213
+ write('This message should not appear')
@@ -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
+ from skilleter_thingy import 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()