msger.py: avoid the trick caused by stream default parameter
[tools/mic.git] / mic / msger.py
1 #!/usr/bin/python -tt
2 # vim: ai ts=4 sts=4 et sw=4
3 #
4 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13 # for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 import os,sys
20 import re
21 from datetime import datetime
22
23 __ALL__ = ['set_mode',
24            'get_loglevel',
25            'set_loglevel',
26            'set_logfile',
27            'raw',
28            'debug',
29            'verbose',
30            'info',
31            'warning',
32            'error',
33            'ask',
34            'pause',
35           ]
36
37 # COLORs in ANSI
38 INFO_COLOR = 32 # green
39 WARN_COLOR = 33 # yellow
40 ERR_COLOR  = 31 # red
41 ASK_COLOR  = 34 # blue
42 NO_COLOR = 0
43
44 PREFIX_RE = re.compile('^<(.*?)>\s*(.*)', re.S)
45
46 INTERACTIVE = True
47
48 LOG_LEVEL = 1
49 LOG_LEVELS = {
50                 'quiet': 0,
51                 'normal': 1,
52                 'verbose': 2,
53                 'debug': 3,
54                 'never': 4,
55              }
56
57 LOG_FILE_FP = None
58 LOG_CONTENT = ''
59 CATCHERR_BUFFILE_FD = -1
60 CATCHERR_BUFFILE_PATH = None
61 CATCHERR_SAVED_2 = -1
62
63 def _general_print(head, color, msg = None, stream = None, level = 'normal'):
64     global LOG_CONTENT
65     if not stream:
66         stream = sys.stdout
67
68     if LOG_LEVELS[level] > LOG_LEVEL:
69         # skip
70         return
71
72     if CATCHERR_BUFFILE_FD > 0:
73         size = os.lseek(CATCHERR_BUFFILE_FD , 0, os.SEEK_END)
74         os.lseek(CATCHERR_BUFFILE_FD, 0, os.SEEK_SET)
75         errormsg = os.read(CATCHERR_BUFFILE_FD, size)
76         os.ftruncate(CATCHERR_BUFFILE_FD, 0)
77         msg += errormsg
78
79     if LOG_FILE_FP:
80         save_msg = msg.strip()
81         if save_msg:
82             timestr = datetime.now().strftime('[%m/%d %H:%M:%S] ')
83             LOG_CONTENT += timestr + save_msg + '\n'
84
85     _color_print(head, color, msg, stream, level)
86
87 def _color_print(head, color, msg, stream, level):
88     colored = True
89     if color == NO_COLOR or \
90        not stream.isatty() or \
91        os.getenv('ANSI_COLORS_DISABLED') is not None:
92         colored = False
93
94     if head.startswith('\r'):
95         # need not \n at last
96         newline = False
97     else:
98         newline = True
99
100     if colored:
101         head = '\033[%dm%s:\033[0m ' %(color, head)
102         if not newline:
103             # ESC cmd to clear line
104             head = '\033[2K' + head
105     else:
106         if head:
107             head += ': '
108             if head.startswith('\r'):
109                 head = head.lstrip()
110                 newline = True
111
112     if msg is not None:
113         stream.write('%s%s' % (head, msg))
114         if newline:
115             stream.write('\n')
116
117     stream.flush()
118
119 def _color_perror(head, color, msg, level = 'normal'):
120     if CATCHERR_BUFFILE_FD > 0:
121         _general_print(head, color, msg, sys.stdout, level)
122     else:
123         _general_print(head, color, msg, sys.stderr, level)
124
125 def _split_msg(head, msg):
126     if isinstance(msg, list):
127         msg = '\n'.join(map(str, msg))
128
129     if msg.startswith('\n'):
130         # means print \n at first
131         msg = msg.lstrip()
132         head = '\n' + head
133
134     elif msg.startswith('\r'):
135         # means print \r at first
136         msg = msg.lstrip()
137         head = '\r' + head
138
139     m = PREFIX_RE.match(msg)
140     if m:
141         head += ' <%s>' % m.group(1)
142         msg = m.group(2)
143
144     return head, msg
145
146 def get_loglevel():
147     return (k for k,v in LOG_LEVELS.items() if v==LOG_LEVEL).next()
148
149 def set_loglevel(level):
150     global LOG_LEVEL
151     if level not in LOG_LEVELS:
152         # no effect
153         return
154
155     LOG_LEVEL = LOG_LEVELS[level]
156
157 def set_interactive(mode=True):
158     global INTERACTIVE
159     if mode:
160         INTERACTIVE = True
161     else:
162         INTERACTIVE = False
163
164 def raw(msg=''):
165     _general_print('', NO_COLOR, msg)
166
167 def info(msg):
168     head, msg = _split_msg('Info', msg)
169     _general_print(head, INFO_COLOR, msg)
170
171 def verbose(msg):
172     head, msg = _split_msg('Verbose', msg)
173     _general_print(head, INFO_COLOR, msg, level = 'verbose')
174
175 def warning(msg):
176     head, msg = _split_msg('Warning', msg)
177     _color_perror(head, WARN_COLOR, msg)
178
179 def debug(msg):
180     head, msg = _split_msg('Debug', msg)
181     _color_perror(head, ERR_COLOR, msg, level = 'debug')
182
183 def error(msg):
184     head, msg = _split_msg('Error', msg)
185     _color_perror(head, ERR_COLOR, msg)
186     sys.exit(1)
187
188 def ask(msg, default=True):
189     _general_print('\rQ', ASK_COLOR, '')
190     try:
191         if default:
192             msg += '(Y/n) '
193         else:
194             msg += '(y/N) '
195         if INTERACTIVE:
196             while True:
197                 repl = raw_input(msg)
198                 if repl.lower() == 'y':
199                     return True
200                 elif repl.lower() == 'n':
201                     return False
202                 elif not repl.strip():
203                     # <Enter>
204                     return default
205
206                 # else loop
207         else:
208             if default:
209                 msg += ' Y'
210             else:
211                 msg += ' N'
212             _general_print('', NO_COLOR, msg)
213
214             return default
215     except KeyboardInterrupt:
216         sys.stdout.write('\n')
217         sys.exit(2)
218
219 def pause(msg=None):
220     if INTERACTIVE:
221         _general_print('\rQ', ASK_COLOR, '')
222         if msg is None:
223             msg = 'press <ENTER> to continue ...'
224         raw_input(msg)
225
226 def set_logfile(fpath):
227     global LOG_FILE_FP
228
229     def _savelogf():
230         if LOG_FILE_FP:
231             if not os.path.exists(os.path.dirname(LOG_FILE_FP)):
232                 os.makedirs(os.path.dirname(LOG_FILE_FP))
233             fp = open(LOG_FILE_FP, 'w')
234             fp.write(LOG_CONTENT)
235             fp.close()
236
237     if LOG_FILE_FP is not None:
238         warning('duplicate log file configuration')
239
240     LOG_FILE_FP = os.path.abspath(os.path.expanduser(fpath))
241
242     import atexit
243     atexit.register(_savelogf)
244
245 def enable_logstderr(fpath):
246     global CATCHERR_BUFFILE_FD
247     global CATCHERR_BUFFILE_PATH
248     global CATCHERR_SAVED_2
249
250     if os.path.exists(fpath):
251         os.remove(fpath)
252     CATCHERR_BUFFILE_PATH = fpath
253     CATCHERR_BUFFILE_FD = os.open(CATCHERR_BUFFILE_PATH, os.O_RDWR|os.O_CREAT)
254     CATCHERR_SAVED_2 = os.dup(2)
255     os.dup2(CATCHERR_BUFFILE_FD, 2)
256
257 def disable_logstderr():
258     global CATCHERR_BUFFILE_FD
259     global CATCHERR_BUFFILE_PATH
260     global CATCHERR_SAVED_2
261
262     os.dup2(CATCHERR_SAVED_2, 2)
263     os.close(CATCHERR_SAVED_2)
264     os.close(CATCHERR_BUFFILE_FD)
265     os.unlink(CATCHERR_BUFFILE_PATH)
266     CATCHERR_BUFFILE_FD = -1
267     CATCHERR_BUFFILE_PATH = None
268     CATCHERR_SAVED_2 = -1