Smarter krb5 cache initialisation.
[profile.git] / terminal_colors
1 #!/usr/bin/env python
2
3 """
4 Version 1.0
5
6 Displays 256, 88 and 16 color tables depending on what the terminal supports.
7 Also provides for conversion between 256 and 88 color values.
8
9 Note on coding style. I was playing around with using classes as simple
10 namespaces (sort of like modules); ie. having classes that have all
11 staticmethods and never get instatiated.
12
13 """
14
15 import curses
16 from optparse import OptionParser, make_option
17 from math import ceil, sqrt
18
19 option_list = [
20     make_option("-x", "--hex", action="store_true", dest="hex",
21         default=False, help="Include hex color numbers on chart."),
22     make_option("-n", "--numbers", action="store_true", dest="numbers",
23         default=False, help="Include color escape numbers on chart."),
24     make_option("-b", "--block", action="store_true", dest="block",
25         default=True, help="Display as block format (vs cube) [default]."),
26     make_option("-c", "--cube-slice", action="store_true", dest="cube",
27         default=False, help="Display as cube slices (vs block)."),
28     make_option("-v", "--vertical", action="store_true", dest="vertical",
29         default=True, help="Display with vertical orientation [default]."),
30     make_option("-z", "--horizontal", action="store_true", dest="horizontal",
31         default=False, help="Display with horizontal orientation."),
32     make_option("-l", "--rgb", action="store_true", dest="rgb",
33         default=False, help="Long format. RGB values text."),
34     make_option("-r", "--256to88", action="store", dest="reduce",
35         metavar="N", type="int",
36         help="Convert (reduce) 256 color value N to an 88 color value."),
37     make_option("-e", "--88to256", action="store", dest="expand",
38         metavar="N", type="int",
39         help="Convert (expand) 88 color value N to an 256 color value."),
40     ]
41
42 version = __doc__.split('\n')[1]
43 parser = OptionParser(version=version, option_list=option_list)
44 (options, args) = parser.parse_args()
45
46 # output constants
47 fg_escape = "\x1b[38;5;%dm"
48 bg_escape = "\x1b[48;5;%dm"
49 clear = "\x1b[0m"
50
51 class _staticmethods(type):
52     """ Got tired of adding @staticmethod in front of every method.
53     """
54     def __new__(m, n, b, d):
55         """ turn all methods in to staticmethods.
56             staticmethod() deals correctly with class attributes.
57         """
58         for (n, f) in d.items():
59             d[n] = staticmethod(f)
60         return type.__new__(m, n, b, d)
61
62
63 class term16(object):
64     """ Basic 16 color terminal.
65     """
66
67     __metaclass__ = _staticmethods
68
69     def _label():
70         if options.numbers:
71             return " %2d "
72         elif options.hex:
73             return " %2x "
74         return "  "
75
76     def fg(label, n):
77         fg = n < 8 and 15 or 0
78         try:
79             return fg_escape % fg + label % n + clear
80         except TypeError:
81             return fg_escape % fg + label + clear
82
83     def _color_table():
84         label = term16._label()
85         return [
86                 [bg_escape % n + term16.fg(label, n) + clear
87                     for n in range(8)],
88                 [bg_escape % n + term16.fg(label, n) + clear
89                     for n in range(8,16)]
90             ]
91
92     def display():
93         """ display 16 color info
94         """
95         print "System colors:"
96         colors = term16._color_table()
97         for r in colors:
98             print ''.join(i for i in r)
99
100 class term256(term16):
101     """ eg. xterm-256
102     """
103
104     def _rgb_lookup():
105         """ color rgb lookup dict
106         """
107         rgb = "%02x/%02x/%02x"
108         cincr = [0] + [95+40*n for n in range(5)]
109         color_rgb = [rgb % (i, j, k)
110                 for i in cincr for j in cincr for k in cincr]
111         color_rgb = dict(zip(range(16, len(color_rgb)+16), color_rgb))
112         greys = [rgb % (((8+n),)*3) for n in range(0, 240, 10)]
113         greys = dict(zip(range(232, 256), greys))
114         color_rgb.update(greys)
115         return color_rgb
116
117     def _rgb_color_table():
118         """ 256 color info
119         """
120         label = "% 4d: %s"
121         _rgb = term256._rgb_lookup()
122         return [[fg_escape % n + label % (n, _rgb[n]) + clear
123                 for n in [i+j for j in range(6)]]
124                     for i in range(16, 256, 6)]
125
126     def _rgb_display():
127         """ display colors with rgb hex info
128         """
129         colors = term256._rgb_color_table()
130         while colors:
131             rows, colors = colors[:6], colors[6:]
132             for r in zip(*rows):
133                 print ''.join(i for i in r)
134             print
135
136     def _label():
137         if options.numbers:
138             return "%3d "
139         elif options.hex:
140             return " %2x "
141         return "  "
142
143     def fg(label, n):
144         if n < 232:
145             fg = n < 124 and 15 or 0
146         else:
147             fg = n < 244 and 15 or 0
148         try:
149             return fg_escape % fg + label % n + clear
150         except TypeError:
151             return fg_escape % fg + label + clear
152
153     def _color_table():
154         """ compact 256 color info
155         """
156         label = term256._label()
157         return [[bg_escape % n + term256.fg(label, n) + clear
158                 for n in [i+j for j in range(6)]]
159                     for i in range(16, 232, 6)]
160
161     def _grey_table():
162         """ compact grey table
163         """
164         label = " " + term256._label()
165         return [[bg_escape % n + term256.fg(label, n) + clear
166                 for n in [i+j for j in range(12)]]
167                     for i in range(232, 256, 12)]
168
169     def _compact_display():
170         """ display colors in compact format
171         """
172         colors = term256._color_table()
173         if options.cube:
174             _cube(colors)
175         elif options.block:
176             _block(colors)
177         print
178         print "Greyscale ramp:"
179         greys  = term256._grey_table()
180         for r in greys:
181             print ''.join(i for i in r)
182
183     def display():
184         """ display 256 color info (+ 16 in compact format)
185         """
186         if options.rgb:
187             print "Xterm RGB values for 6x6x6 color cube and greyscale."
188             print
189             term256._rgb_display()
190         else:
191             term16.display()
192             print
193             print "6x6x6 color cube:"
194             term256._compact_display()
195
196
197 class term88(term16):
198     """ xterm-88 or urxvt
199     """
200
201     def _rgb_lookup():
202         """ color rgb lookup dict
203         """
204         rgb = "%02x/%02x/%02x"
205         cincr = [0, 0x8b, 0xcd, 0xff]
206         color_rgb = [rgb % (i, j, k)
207                 for i in cincr for j in cincr for k in cincr]
208         color_rgb = dict(zip(range(16, len(color_rgb)+16), color_rgb))
209         greys = [rgb % ((n,)*3)
210                 for n in [0x2e, 0x5c, 0x73, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7]]
211         greys = dict(zip(range(80, 88), greys))
212         color_rgb.update(greys)
213         return color_rgb
214
215     def _rgb_color_table():
216         """ 256 color info
217         """
218         label = "% 4d: %s"
219         _rgb = term88._rgb_lookup()
220         return [[fg_escape % n + label % (n, _rgb[n]) + clear
221                 for n in [i+j for j in range(4)]]
222                     for i in range(16, 88, 4)]
223
224     def _rgb_display():
225         """ display colors with rgb hex info
226         """
227         colors = term88._rgb_color_table()
228         while colors:
229             rows, colors = colors[:4], colors[4:]
230             for r in zip(*rows):
231                 print ''.join(i for i in r)
232             print
233
234     def _label():
235         if options.numbers:
236             return " %2d "
237         elif options.hex:
238             return " %2x "
239         return "  "
240
241     def fg(label, n):
242         if n < 80:
243             fg = n < 48 and 15 or 0
244         else:
245             fg = n < 84 and 15 or 0
246         try:
247             return fg_escape % fg + label % n + clear
248         except TypeError:
249             return fg_escape % fg + label + clear
250
251     def _color_table():
252         """ 88 color info
253         """
254         label = term88._label()
255         return [[bg_escape % n + term88.fg(label, n) + clear
256                 for n in [i+j for j in range(4)]]
257                     for i in range(16, 80, 4)]
258
259     def _grey_table():
260         """ 88 color grey info
261         """
262         label = term88._label()
263         return [bg_escape % n + term88.fg(label, n) + clear
264                 for n in range(80, 88)]
265
266     def display():
267         """ display 16 + 88 color info
268         """
269         if options.rgb:
270             print "Xterm RGB values for 4x4x4 color cube and greyscale."
271             print
272             term88._rgb_display()
273         else:
274             term16.display()
275             print
276             print "4x4x4 color cube:"
277             colors = term88._color_table()
278             if options.cube:
279                 _cube(colors)
280             elif options.block:
281                 _block(colors)
282             print
283             print "Greyscale ramp:"
284             greys  = term88._grey_table()
285             print ''.join(i for i in greys)
286
287 def _cube(colors):
288     if options.horizontal:
289         def _horizontal(colors):
290             size = int(sqrt(len(colors)))
291             for n in (n*size for n in range(size)):
292                 colors[n:n+size] = zip(*colors[n:n+size])
293             while colors:
294                 rows, colors = colors[:size*2], colors[size*2:]
295                 for n in range(size):
296                     print ''.join(i
297                             for i in rows[n]+tuple(reversed(rows[n+size])))
298                 if colors: print
299         _horizontal(colors)
300     else: #options.vertical - default
301         def _vertical(colors):
302             size = int(sqrt(len(colors)))
303             top = [colors[n:len(colors):size*2] for n in range(size)]
304             bottom = [colors[n+size:len(colors):size*2]
305                     for n in reversed(range(size))]
306             for group in [top, bottom]:
307                 for rows in group:
308                     for r in rows:
309                         print ''.join(i for i in r),
310                     print
311         _vertical(colors)
312
313 def _block(colors):
314     size = int(sqrt(len(colors)))
315     if not options.horizontal:
316         for n in (n*size for n in range(size)):
317             colors[n:n+size] = zip(*colors[n:n+size])
318     while colors:
319         half = size*(size/2)
320         rows, colors = colors[:half], colors[half:]
321         for n in range(size):
322             for r in rows[n:len(rows):size]:
323                 print ''.join(i for i in r),
324             print
325         if colors: print
326
327 def convert88to256(n):
328     """ 88 (4x4x4) color cube to 256 (6x6x6) color cube values
329     """
330     if n < 16:
331         return n
332     elif n > 79:
333         return 234 + (3 * (n - 80))
334     else:
335         def m(n):
336             "0->0, 1->1, 2->3, 3->5"
337             return n and n + n-1 or n
338         b = n - 16
339         x = b % 4 
340         y = (b / 4) % 4
341         z = b / 16
342         return 16 + m(x) + (6 * m(y) + 36 * m(z))
343
344 def convert256to88(n):
345     """ 256 (6x6x6) color cube to 88 (4x4x4) color cube values
346     """
347     if n < 16:
348         return n
349     elif n > 231:
350         if n < 234:
351             return 0
352         return 80 + ((n - 234) / 3)
353     else:
354         def m(n, _ratio=(4./6.)):
355             if n < 2:
356                 return int(ceil(_ratio*n))
357             else:
358                 return int(_ratio*n)
359         b = n - 16
360         x = b % 6 
361         y = (b / 6) % 6
362         z = b / 36
363         return 16 + m(x) + (4 * m(y) + 16 * m(z))
364
365 def _terminal():
366     """ detect # of colors supported by terminal and return appropriate
367         terminal class
368     """
369     curses.setupterm()
370     num_colors = curses.tigetnum('colors')
371     if num_colors > 0:
372         return {16:term16, 88:term88, 256:term256}.get(num_colors, term16)
373
374 def main():
375     if options.reduce:
376         v = convert256to88(options.reduce)
377         # reconvert back to display reduction in context
378         print "%s (equivalent to 256 value: %s)" % (v, convert88to256(v))
379     elif options.expand:
380         print convert88to256(options.expand)
381     else:
382         term = _terminal()
383         if term is None:
384             print "Your terminal reports that it has no color support."
385         else:
386             term.display()
387
388 if __name__ == "__main__":
389     main()
390