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,101 @@
1
+ #! /usr/bin/env python3
2
+
3
+ """
4
+ Read JSON Terraform output and convert back to human-readable text
5
+ This allows multiple errors and warnings to be reported as there's
6
+ no way of doing this directly from Terraform
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import json
12
+ import argparse
13
+ from collections import defaultdict
14
+
15
+ import thingy.colour as colour
16
+
17
+ ################################################################################
18
+
19
+ def error(msg, status=1):
20
+ """Report an error and quit"""
21
+
22
+ colour.write(f'[RED:ERROR]: {msg}')
23
+ sys.exit(status)
24
+
25
+ ################################################################################
26
+
27
+ def main():
28
+ """Everything"""
29
+
30
+ # Command line is either empty or contains the input file
31
+
32
+ parser = argparse.ArgumentParser(description='Convert Terraform JSON output back into human-readable text')
33
+ parser.add_argument('--abspath', '-a', action='store_true', help='Output absolute file paths')
34
+ parser.add_argument('infile', nargs='*', help='The error file (defaults to standard input if not specified)')
35
+
36
+ args = parser.parse_args()
37
+
38
+ # Open the input file or use stdin and read the JSON
39
+
40
+ jsonfile = open(sys.argv[1], 'rt') if args.infile else sys.stdin
41
+
42
+ terraform = json.loads(jsonfile.read())
43
+
44
+ # Collect each of the error/warnings
45
+
46
+ report = defaultdict(list)
47
+
48
+ if 'diagnostics' in terraform:
49
+ for diagnostics in terraform['diagnostics']:
50
+ severity = diagnostics['severity'].title()
51
+
52
+ if 'range' in diagnostics:
53
+ file_path = os.path.abspath(diagnostics['range']['filename']) if args.abspath else diagnostics['range']['filename']
54
+
55
+ category = f'{severity}: {diagnostics["summary"]} - {diagnostics["detail"]}'
56
+
57
+ message = ''
58
+ if 'range' in diagnostics:
59
+ message += f'In [BLUE:{file_path}:{diagnostics["range"]["start"]["line"]}]'
60
+
61
+ if 'address' in diagnostics:
62
+ message += f' in [BLUE:{diagnostics["address"]}]'
63
+
64
+ report[category].append(message)
65
+
66
+ for category in report:
67
+ colour.write()
68
+
69
+ # Fudge emboldening multi-line warnings
70
+
71
+ formatted_category = '[BOLD:' + category.replace('\n', ']\n[BOLD:') + ']'
72
+ colour.write(formatted_category)
73
+
74
+ for entry in sorted(report[category]):
75
+ colour.write(f' {entry}')
76
+
77
+ # Summarise the results
78
+
79
+ error_count = terraform.get('error_count', 0)
80
+ warning_count = terraform.get('warning_count', 0)
81
+
82
+ colour.write()
83
+ colour.write(f'[BOLD:Summary:] [BLUE:{error_count}] [BOLD:errors and] [BLUE:{warning_count}] [BOLD:warnings]')
84
+
85
+ ################################################################################
86
+
87
+ def tfparse():
88
+ """Entry point"""
89
+
90
+ try:
91
+ main()
92
+
93
+ except KeyboardInterrupt:
94
+ sys.exit(1)
95
+ except BrokenPipeError:
96
+ sys.exit(2)
97
+
98
+ ################################################################################
99
+
100
+ if __name__ == '__main__':
101
+ tfparse()
@@ -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,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,278 @@
1
+ #! /usr/bin/env python3
2
+
3
+ """ Convert colour highlighting codes from the LS_COLORS environment variable
4
+ used by ls to curses
5
+ """
6
+
7
+ ################################################################################
8
+
9
+ import sys
10
+ import os
11
+ import glob
12
+ import fnmatch
13
+ import curses
14
+ import stat
15
+
16
+ ################################################################################
17
+
18
+ class CursesDircolors:
19
+ """ Convert dircolors codes to curses colours """
20
+
21
+ # Convert standard foreground and background codes to curses equivalents
22
+
23
+ ANSI_CONVERT_FORE = {
24
+ 30: curses.COLOR_BLACK,
25
+ 31: curses.COLOR_RED,
26
+ 32: curses.COLOR_GREEN,
27
+ 33: curses.COLOR_YELLOW,
28
+ 34: curses.COLOR_BLUE,
29
+ 35: curses.COLOR_MAGENTA,
30
+ 36: curses.COLOR_CYAN,
31
+ 37: curses.COLOR_WHITE,
32
+ }
33
+
34
+ ANSI_CONVERT_BACK = {
35
+ 40: curses.COLOR_BLACK,
36
+ 41: curses.COLOR_RED,
37
+ 42: curses.COLOR_GREEN,
38
+ 43: curses.COLOR_YELLOW,
39
+ 44: curses.COLOR_BLUE,
40
+ 45: curses.COLOR_MAGENTA,
41
+ 46: curses.COLOR_CYAN,
42
+ 47: curses.COLOR_WHITE,
43
+ }
44
+
45
+ # Convert attribute codes to their meanings
46
+ # TODO: Attributes not handled yet
47
+
48
+ ANSI_CONVERT_ATTR = {
49
+ 0: 0,
50
+ 1: curses.A_BOLD,
51
+ 4: curses.A_UNDERLINE,
52
+ 5: curses.A_BLINK,
53
+ 7: curses.A_BLINK,
54
+ 8: curses.A_INVIS,
55
+ }
56
+
57
+ # Default colour
58
+
59
+ DEFAULT_ATTR = {'attr': [], 'fore': -1, 'back': -1}
60
+
61
+ ################################################################################
62
+
63
+ def __init__(self, reserved=0):
64
+ # Create the lookup tables associating special type codes or wildcards
65
+ # with colour pairs.
66
+
67
+ self.colour_pairs = [[-1, -1]]
68
+
69
+ self.wildcard_highlight = {}
70
+ self.special_highlight = {}
71
+
72
+ self.reserved = reserved
73
+
74
+ self.init_ls_colours()
75
+
76
+ ################################################################################
77
+
78
+ def curses_alloc_pair(self, attr):
79
+ """ Given a set of attributes return the equivalent curses colour pair,
80
+ creating a new one if a matching one doesn't already exsit """
81
+
82
+ # TODO: Take account of attributes as well as colours
83
+
84
+ colours = [attr['fore'], attr['back']]
85
+
86
+ # Get an existing colour pair that uses the same colours or create
87
+ # a new one if one doesn't exist
88
+
89
+ if colours in self.colour_pairs:
90
+ pair_index = self.colour_pairs.index(colours) + self.reserved
91
+ else:
92
+ pair_index = len(self.colour_pairs) + self.reserved
93
+ self.colour_pairs.append(colours)
94
+ curses.init_pair(pair_index, attr['fore'], attr['back'])
95
+
96
+ return pair_index
97
+
98
+ ################################################################################
99
+
100
+ def curses_colour(self, code):
101
+ """ Return a cursors colour pair index for the specified dircolor colour
102
+ code string. """
103
+
104
+ # Default attribute
105
+
106
+ attr = {'attr': [], 'fore': -1, 'back': -1}
107
+
108
+ # Non-zero if processing multi-value colour code
109
+
110
+ special = 0
111
+ special_item = None
112
+
113
+ # We trigger a ValueError and fail on anything that's wrong in the code
114
+
115
+ try:
116
+ # Split into fields and convert to integer values
117
+
118
+ codes = [int(c) for c in code.split(';')]
119
+
120
+ for entry in codes:
121
+ # Process 2nd entry in a special colour sequence - must have value of 5
122
+
123
+ if special == 1:
124
+ if entry != 5:
125
+ raise ValueError
126
+ special = 2
127
+
128
+ # Process 3rd entry in a special colour sequence - must be the colour
129
+ # code between 0 and 255
130
+
131
+ elif special == 2:
132
+ if entry < 0 or entry > 255:
133
+ raise ValueError
134
+
135
+ attr[special_item] = entry
136
+ special = 0
137
+
138
+ # Normal foreground colour
139
+
140
+ elif entry in self.ANSI_CONVERT_FORE:
141
+ attr['fore'] = self.ANSI_CONVERT_FORE[entry]
142
+
143
+ # Normal background colour
144
+
145
+ elif entry in self.ANSI_CONVERT_BACK:
146
+ attr['back'] = self.ANSI_CONVERT_BACK[entry]
147
+
148
+ # Special foreground colour in the form 38;5;VALUE
149
+
150
+ elif entry == 38:
151
+ special = 1
152
+ special_item = 'fore'
153
+
154
+ # Special background colour in the form 48;5;VALUE
155
+
156
+ elif entry == 48:
157
+ special = 1
158
+ special_item = 'back'
159
+
160
+ # Attribute (underline, bold, etc.)
161
+
162
+ elif entry in self.ANSI_CONVERT_ATTR:
163
+ attr['attr'].append(self.ANSI_CONVERT_ATTR[entry])
164
+
165
+ # Anything else is an error
166
+
167
+ else:
168
+ raise ValueError
169
+
170
+ except ValueError:
171
+ print(f'Invalid colour specification: "{code}"')
172
+ sys.exit(1)
173
+
174
+ # Allocate a colour pair for the colour combination and return it
175
+
176
+ return self.curses_alloc_pair(attr)
177
+
178
+ ################################################################################
179
+
180
+ def init_ls_colours(self):
181
+ """ Generate tables matching special file types (fifos, sockets, etc.) and
182
+ wildcards to curses colour pairs """
183
+
184
+ colour_data = os.environ.get('LS_COLORS', '').split(':')
185
+
186
+ # Iterate through the highlighters, create/get a colour pair corresponding
187
+ # to the colour codes and save one of the tables.
188
+
189
+ for item in colour_data:
190
+ item = item.strip()
191
+ if '=' in item:
192
+ code, colour = item.split('=')
193
+
194
+ colour_pair = self.curses_colour(colour)
195
+
196
+ if len(code) == 2 and '*' not in code and '.' not in code:
197
+ self.special_highlight[code] = colour_pair
198
+ else:
199
+ self.wildcard_highlight[code] = colour_pair
200
+
201
+ ################################################################################
202
+
203
+ def get_colour(self, filename, filemode=None):
204
+ """ Get the curses colour for a filename, returns 0 if no highlighting
205
+ is needed """
206
+
207
+ if filemode:
208
+ if stat.S_ISDIR(filemode):
209
+ if 'di' in self.special_highlight:
210
+ return self.special_highlight['di']
211
+ elif stat.S_ISLNK(filemode):
212
+ destfile = os.readlink(filename)
213
+
214
+ if os.path.exists(destfile):
215
+ if 'ln' in self.special_highlight:
216
+ return self.special_highlight['ln']
217
+ elif 'or' in self.special_highlight:
218
+ return self.special_highlight['or']
219
+
220
+ elif stat.S_ISBLK(filemode):
221
+ if 'bd' in self.special_highlight:
222
+ return self.special_highlight['bd']
223
+ elif stat.S_ISCHR(filemode):
224
+ if 'cd' in self.special_highlight:
225
+ return self.special_highlight['cd']
226
+
227
+ if filemode & stat.S_IXUSR:
228
+ if 'ex' in self.special_highlight:
229
+ return self.special_highlight['ex']
230
+
231
+ for entry in self.wildcard_highlight:
232
+ if fnmatch.fnmatch(filename, entry):
233
+ colour = self.wildcard_highlight[entry]
234
+ break
235
+ else:
236
+ colour = 0
237
+
238
+ return colour
239
+
240
+ ################################################################################
241
+
242
+ def get_colour_pair(self, filename, filemode=None):
243
+ """ Get the curses colour pair for a filename, optionally specifying the
244
+ file mode (as per os.stat()) """
245
+
246
+ return curses.color_pair(self.get_colour(filename, filemode))
247
+
248
+ ################################################################################
249
+
250
+ def _test_code(stdscr):
251
+ """ Entry point """
252
+
253
+ curses.start_color()
254
+ curses.use_default_colors()
255
+
256
+ # Initialise colours
257
+
258
+ dc = CursesDircolors()
259
+
260
+ # Demo code to list files specified by the first command line argument
261
+ # highlighted appropriately
262
+
263
+ y = 0
264
+ for filename in glob.glob(sys.argv[1]):
265
+ colour = dc.get_colour(filename)
266
+
267
+ stdscr.addstr(y, 0, filename, curses.color_pair(colour))
268
+
269
+ y += 1
270
+ if y > 30:
271
+ break
272
+
273
+ stdscr.getch()
274
+
275
+ ################################################################################
276
+
277
+ if __name__ == "__main__":
278
+ curses.wrapper(_test_code)