Sunday, November 1, 2009

Crayon

Colorful text, right? What else could you possibly want?

When you're playing around with your shell you can typically use a bunch of control sequences to set colors and styles to your text, but these are hard to remember... at least, in my experience.

This script should provide a slightly easier way to use those controle sequences. E.g. when you want to have red bold text on white background, you'd normally go:

\033[31m\033[1m\033[47mSome text\033[m

With this script, you can just go:

./crayon.py -c red -s bold -b white "Some text"

You have to admit, it's more intelligible.

Enough banter; the code:
 
1 #!/usr/bin/python
2 #
3 # Crayon
4 #
5 # A simple script to color your text and wotnot. This basically uses
6 # \033-type sequences and color codes that may or may not work with your
7 # shell.
8 #
9 # Parameters:
10 # -s STYLE | --style=STYLE Specify a style (see list below).
11 # -c COLOR | --color=COLOR Specify a color (see list below).
12 # -b COLOR | --background=COLOR Specify a color (see list below).
13 # -k | --keep-style Do not reset styles after echo-ing.
14 # -d | --default Reset settings.
15 # -n | --no-newline Do not append a new line to the result.
16 # -r | --raw Print control sequences without applying them.
17 # -h | --help Print usage information (this).
18 #
19 # Author:
20 # Konrad Siek <konrad.siek@gmail.com>
21 #
22 # License:
23 # Copyright 2009 Konrad Siek
24 #
25 # This program is free software: you can redistribute it and/or modify
26 # it under the terms of the GNU General Public License as published by
27 # the Free Software Foundation, either version 3 of the License, or
28 # (at your option) any later version.
29 #
30 # This program is distributed in the hope that it will be useful,
31 # but WITHOUT ANY WARRANTY; without even the implied warranty of
32 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 # GNU General Public License for more details.
34 #
35 # You should have received a copy of the GNU General Public License
36 # along with this program. If not, see <http://www.gnu.org/licenses/>.
37
38 import sys
39 from getopt import getopt
40
41 # Styles.
42 BOLD = 1
43 UNDERLINE = 4
44 BLINK = 5
45 REVERSE = 7
46 CONCEALED = 8
47
48 # Colors.
49 BLACK = 0
50 RED = 1
51 GREEN = 2
52 YELLOW = 3
53 BLUE = 4
54 MAGENTA = 5
55 CYAN = 6
56 WHITE = 7
57
58 # Color string to code map.
59 COLORS = {
60 'black': BLACK,
61 'red': RED,
62 'green': GREEN,
63 'yellow': YELLOW,
64 'blue': BLUE,
65 'magenta': MAGENTA,
66 'cyan': CYAN,
67 'white': WHITE,
68 }
69
70 # Style to string map.
71 STYLES = {
72 'bold': BOLD,
73 'underline': UNDERLINE,
74 'blink': BLINK,
75 'reverse': REVERSE,
76 'concealed': CONCEALED,
77 }
78
79 def escape(string):
80 r"""Change the \033 character to its sring representation."""
81 return string.replace('\033', '\\033')
82
83 def command(c):
84 """Add the command character sequence to the specified code."""
85 return '\033[%sm' % c
86
87 def style(s):
88 """Apply the given code as a style command sequence."""
89 return command(s)
90
91 def fg(c):
92 """Apply the given code as a foreground color command sequence."""
93 return command(30 + c)
94
95 def bg(c):
96 """Apply the given code as a background color command sequence."""
97 return command(40 + c)
98
99 def default():
100 """Apply an empty command sequence - reset setting to default."""
101 return command('')
102
103 def echo(style, out = sys.stdout):
104 """Print out the string to standard output or some other stream."""
105 out.write(style)
106
107 def color_of_str(s):
108 """Translate the string to a color code."""
109 if s.isdigit() and int(s) in COLORS.values():
110 return int(s)
111 if s.lower() in COLORS:
112 return COLORS[s.lower()]
113 raise Exception("Unrecognized color: %s" % s);
114
115 def style_of_str(s):
116 """Translate the string to a string code."""
117 if s.isdigit() and int(s) in STYLES.values():
118 return int(s)
119 if s.lower() in STYLES:
120 return STYLES[s.lower()]
121 raise Exception("Unrecognized style: %s" % s);
122
123 def usage(command_name):
124 """Print script usage information."""
125 print 'Usage: %s [OPTIONS] TEXT' % command_name
126 print 'Options:'
127 shorts = ['s:', 'c:', 'b:', 'r', 'd', 'h', 'k', 'n']
128 longs = [
129 'style=', 'color=', 'background=', 'keep-style',
130 'default', 'help=', 'raw', 'no-newline'
131 ]
132 print "\t-s STYLE | --style=STYLE\tSpecify a style (see list below)."
133 print "\t-c COLOR | --color=COLOR\tSpecify a color (see list below)."
134 print "\t-b COLOR | --background=COLOR\tSpecify a color (see list below)."
135 print "\t-k | --keep-style\t\tDo not reset styles after echo-ing."
136 print "\t-d | --default\t\t\tReset settings."
137 print "\t-n | --no-newline\t\tDo not append a new line to the result."
138 print "\t-r | --raw\t\t\tPrint control sequences without applying them."
139 print "\t-h | --help\t\t\tPrint usage information (this)."
140 print "Styles (use either names or codes):"
141 for style in STYLES.items():
142 print "\t%s - %s\t" % style
143 print "Colors (use either names or codes):"
144 for color in COLORS.items():
145 print "\t%s - %s\t" % color
146
147 if __name__ == '__main__':
148 shorts = ['s:', 'c:', 'b:', 'r', 'd', 'h', 'k', 'n']
149 longs = [
150 'style=', 'color=', 'background=', 'keep-style',
151 'default', 'help', 'raw', 'no-newline'
152 ]
153
154 opts, args = getopt(sys.argv[1:], ''.join(shorts), longs)
155
156 prefix = ''
157 postfix = default()
158 raw = False
159 newline = True
160
161 for opt in opts:
162 if opt[0] in ['-c', '--color']:
163 prefix += fg(color_of_str(opt[1]))
164 elif opt[0] in ['-b', '--background']:
165 prefix += bg(color_of_str(opt[1]))
166 elif opt[0] in ['-s', '--style']:
167 prefix += style(style_of_str(opt[1]))
168 elif opt[0] in ['-k', '--keep-style']:
169 postfix = ''
170 elif opt[0] in ['-r', '--raw']:
171 raw = True
172 elif opt[0] in ['-d', '--default']:
173 prefix = default()
174 postfix = ''
175 elif opt[0] in ['-n', '--no-newline']:
176 newline = False
177 else:
178 usage(sys.argv[0])
179 sys.exit(1)
180
181 string = prefix + ' '.join(args) + postfix
182 if raw:
183 string = escape(string)
184 if newline:
185 string += "\n"
186 echo(string)


The code is also available at GitHub as python/crayon.py.