]>
Commit | Line | Data |
---|---|---|
b32b8144 | 1 | import logging |
d2e6a577 FG |
2 | import sys |
3 | ||
4 | ||
494da23a TL |
5 | terminal_logger = logging.getLogger('terminal') |
6 | ||
7 | ||
d2e6a577 FG |
8 | class colorize(str): |
9 | """ | |
10 | Pretty simple to use:: | |
11 | ||
12 | colorize.make('foo').bold | |
13 | colorize.make('foo').green | |
14 | colorize.make('foo').yellow | |
15 | colorize.make('foo').red | |
16 | colorize.make('foo').blue | |
17 | ||
18 | Otherwise you could go the long way (for example if you are | |
19 | testing this class):: | |
20 | ||
21 | string = colorize('foo') | |
22 | string._set_attributes() | |
23 | string.red | |
24 | ||
25 | """ | |
26 | ||
27 | def __init__(self, string): | |
d2e6a577 FG |
28 | self.appends = '' |
29 | self.prepends = '' | |
494da23a | 30 | self.isatty = sys.__stderr__.isatty() |
d2e6a577 FG |
31 | |
32 | def _set_attributes(self): | |
33 | """ | |
34 | Sets the attributes here because the str class does not | |
35 | allow to pass in anything other than a string to the constructor | |
36 | so we can't really mess with the other attributes. | |
37 | """ | |
38 | for k, v in self.__colors__.items(): | |
39 | setattr(self, k, self.make_color(v)) | |
40 | ||
41 | def make_color(self, color): | |
42 | if not self.isatty: | |
43 | return self | |
44 | return color + self + '\033[0m' + self.appends | |
45 | ||
46 | @property | |
47 | def __colors__(self): | |
48 | return dict( | |
49 | blue='\033[34m', | |
50 | green='\033[92m', | |
51 | yellow='\033[33m', | |
52 | red='\033[91m', | |
53 | bold='\033[1m', | |
54 | ends='\033[0m' | |
55 | ) | |
56 | ||
57 | @classmethod | |
58 | def make(cls, string): | |
59 | """ | |
60 | A helper method to return itself and workaround the fact that | |
61 | the str object doesn't allow extra arguments passed in to the | |
62 | constructor | |
63 | """ | |
64 | obj = cls(string) | |
65 | obj._set_attributes() | |
66 | return obj | |
67 | ||
68 | # | |
69 | # Common string manipulations | |
70 | # | |
71 | yellow = lambda x: colorize.make(x).yellow # noqa | |
72 | blue = lambda x: colorize.make(x).blue # noqa | |
73 | green = lambda x: colorize.make(x).green # noqa | |
74 | red = lambda x: colorize.make(x).red # noqa | |
75 | bold = lambda x: colorize.make(x).bold # noqa | |
76 | red_arrow = red('--> ') | |
77 | blue_arrow = blue('--> ') | |
78 | green_arrow = green('--> ') | |
79 | yellow_arrow = yellow('--> ') | |
80 | ||
81 | ||
82 | class _Write(object): | |
83 | ||
84 | def __init__(self, _writer=None, prefix='', suffix='', flush=False): | |
494da23a TL |
85 | # we can't set sys.stderr as the default for _writer. otherwise |
86 | # pytest's capturing gets confused | |
87 | self._writer = _writer or sys.stderr | |
d2e6a577 FG |
88 | self.suffix = suffix |
89 | self.prefix = prefix | |
90 | self.flush = flush | |
91 | ||
92 | def bold(self, string): | |
93 | self.write(bold(string)) | |
94 | ||
95 | def raw(self, string): | |
96 | if not string.endswith('\n'): | |
97 | string = '%s\n' % string | |
98 | self.write(string) | |
99 | ||
100 | def write(self, line): | |
494da23a TL |
101 | entry = self.prefix + line + self.suffix |
102 | ||
103 | try: | |
104 | self._writer.write(entry) | |
105 | if self.flush: | |
106 | self._writer.flush() | |
107 | except (UnicodeDecodeError, UnicodeEncodeError): | |
108 | try: | |
109 | terminal_logger.info(entry.strip('\n')) | |
110 | except (AttributeError, TypeError): | |
111 | terminal_logger.info(entry) | |
d2e6a577 FG |
112 | |
113 | ||
114 | def stdout(msg): | |
115 | return _Write(prefix=blue(' stdout: ')).raw(msg) | |
116 | ||
117 | ||
118 | def stderr(msg): | |
119 | return _Write(prefix=yellow(' stderr: ')).raw(msg) | |
120 | ||
121 | ||
122 | def write(msg): | |
123 | return _Write().raw(msg) | |
124 | ||
125 | ||
126 | def error(msg): | |
127 | return _Write(prefix=red_arrow).raw(msg) | |
128 | ||
129 | ||
b32b8144 FG |
130 | def info(msg): |
131 | return _Write(prefix=blue_arrow).raw(msg) | |
132 | ||
133 | ||
134 | def debug(msg): | |
135 | return _Write(prefix=blue_arrow).raw(msg) | |
136 | ||
137 | ||
d2e6a577 FG |
138 | def warning(msg): |
139 | return _Write(prefix=yellow_arrow).raw(msg) | |
140 | ||
141 | ||
142 | def success(msg): | |
143 | return _Write(prefix=green_arrow).raw(msg) | |
144 | ||
145 | ||
b32b8144 FG |
146 | class MultiLogger(object): |
147 | """ | |
148 | Proxy class to be able to report on both logger instances and terminal | |
149 | messages avoiding the issue of having to call them both separately | |
150 | ||
151 | Initialize it in the same way a logger object:: | |
152 | ||
153 | logger = terminal.MultiLogger(__name__) | |
154 | """ | |
155 | ||
156 | def __init__(self, name): | |
157 | self.logger = logging.getLogger(name) | |
158 | ||
159 | def _make_record(self, msg, *args): | |
160 | if len(str(args)): | |
161 | try: | |
162 | return msg % args | |
163 | except TypeError: | |
164 | self.logger.exception('unable to produce log record: %s' % msg) | |
165 | return msg | |
166 | ||
167 | def warning(self, msg, *args): | |
168 | record = self._make_record(msg, *args) | |
169 | warning(record) | |
170 | self.logger.warning(record) | |
171 | ||
172 | def debug(self, msg, *args): | |
173 | record = self._make_record(msg, *args) | |
174 | debug(record) | |
175 | self.logger.debug(record) | |
176 | ||
177 | def info(self, msg, *args): | |
178 | record = self._make_record(msg, *args) | |
179 | info(record) | |
180 | self.logger.info(record) | |
181 | ||
182 | def error(self, msg, *args): | |
183 | record = self._make_record(msg, *args) | |
184 | error(record) | |
185 | self.logger.error(record) | |
186 | ||
187 | ||
d2e6a577 FG |
188 | def dispatch(mapper, argv=None): |
189 | argv = argv or sys.argv | |
190 | for count, arg in enumerate(argv, 1): | |
191 | if arg in mapper.keys(): | |
192 | instance = mapper.get(arg)(argv[count:]) | |
193 | if hasattr(instance, 'main'): | |
194 | instance.main() | |
195 | raise SystemExit(0) | |
196 | ||
197 | ||
198 | def subhelp(mapper): | |
199 | """ | |
200 | Look at every value of every key in the mapper and will output any | |
201 | ``class.help`` possible to return it as a string that will be sent to | |
494da23a | 202 | stderr. |
d2e6a577 FG |
203 | """ |
204 | help_text_lines = [] | |
205 | for key, value in mapper.items(): | |
206 | try: | |
207 | help_text = value.help | |
208 | except AttributeError: | |
209 | continue | |
210 | help_text_lines.append("%-24s %s" % (key, help_text)) | |
211 | ||
212 | if help_text_lines: | |
213 | return "Available subcommands:\n\n%s" % '\n'.join(help_text_lines) | |
214 | return '' |