1# #START_LICENSE########################################################### 2# 3# 4# This file is part of the Environment for Tree Exploration program 5# (ETE). http://etetoolkit.org 6# 7# ETE is free software: you can redistribute it and/or modify it 8# under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# ETE is distributed in the hope that it will be useful, but WITHOUT 13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 15# License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with ETE. If not, see <http://www.gnu.org/licenses/>. 19# 20# 21# ABOUT THE ETE PACKAGE 22# ===================== 23# 24# ETE is distributed under the GPL copyleft license (2008-2015). 25# 26# If you make use of ETE in published work, please cite: 27# 28# Jaime Huerta-Cepas, Joaquin Dopazo and Toni Gabaldon. 29# ETE: a python Environment for Tree Exploration. Jaime BMC 30# Bioinformatics 2010,:24doi:10.1186/1471-2105-11-24 31# 32# Note that extra references to the specific methods implemented in 33# the toolkit may be available in the documentation. 34# 35# More info at http://etetoolkit.org. Contact: huerta@embl.de 36# 37# 38# #END_LICENSE############################################################# 39from __future__ import absolute_import 40from __future__ import print_function 41 42import sys 43import os 44import re 45import time 46 47from signal import signal, SIGWINCH, SIGKILL, SIGTERM 48from collections import deque 49from textwrap import TextWrapper 50 51import six.moves.queue 52import threading 53 54from .logger import get_main_log 55from .utils import GLOBALS, clear_tempdir, terminate_job_launcher, pjoin, pexist 56from .errors import * 57import six 58from six import StringIO 59 60MAIN_LOG = False 61 62# try: 63# import curses 64# except ImportError: 65# NCURSES = False 66# else: 67# NCURSES = True 68NCURSES = False 69 70# CONVERT shell colors to the same curses palette 71SHELL_COLORS = { 72 "10": '\033[1;37;41m', # white on red 73 "11": '\033[1;37;43m', # white on orange 74 "12": '\033[1;37;45m', # white on magenta 75 "16": '\033[1;37;46m', # white on blue 76 "13": '\033[1;37;40m', # black on white 77 "06": '\033[1;34m', # light blue 78 "05": '\033[1;31m', # light red 79 "03": '\033[1;32m', # light green 80 "8": '\033[1;33m', # yellow 81 "7": '\033[36m', # cyan 82 "6": '\033[34m', # blue 83 "3": '\033[32m', # green 84 "4": '\033[33m', # orange 85 "5": '\033[31m', # red 86 "2": "\033[35m", # magenta 87 "1": "\033[0m", # white 88 "0": "\033[0m", # end 89} 90 91def safe_int(x): 92 try: 93 return int(x) 94 except TypeError: 95 return x 96 97def shell_colorify_match(match): 98 return SHELL_COLORS[match.groups()[2]] 99 100class ExcThread(threading.Thread): 101 def __init__(self, bucket, *args, **kargs): 102 threading.Thread.__init__(self, *args, **kargs) 103 self.bucket = bucket 104 105 def run(self): 106 try: 107 threading.Thread.run(self) 108 except Exception: 109 self.bucket.put(sys.exc_info()) 110 raise 111 112class Screen(StringIO): 113 # tags used to control color of strings and select buffer 114 TAG = re.compile("@@((\d+),)?(\d+):", re.MULTILINE) 115 def __init__(self, windows): 116 StringIO.__init__(self) 117 self.windows = windows 118 self.autoscroll = {} 119 self.pos = {} 120 self.lines = {} 121 self.maxsize = {} 122 self.stdout = None 123 self.logfile = None 124 self.wrapper = TextWrapper(width=80, initial_indent="", 125 subsequent_indent=" ", 126 replace_whitespace=False) 127 128 129 if NCURSES: 130 for windex in windows: 131 h, w = windows[windex][0].getmaxyx() 132 self.maxsize[windex] = (h, w) 133 self.pos[windex] = [0, 0] 134 self.autoscroll[windex] = True 135 self.lines[windex] = 0 136 137 def scroll(self, win, vt, hz=0, refresh=True): 138 line, col = self.pos[win] 139 140 hz_pos = col + hz 141 if hz_pos < 0: 142 hz_pos = 0 143 elif hz_pos >= 1000: 144 hz_pos = 999 145 146 vt_pos = line + vt 147 if vt_pos < 0: 148 vt_pos = 0 149 elif vt_pos >= 1000: 150 vt_pos = 1000 - 1 151 152 if line != vt_pos or col != hz_pos: 153 self.pos[win] = [vt_pos, hz_pos] 154 if refresh: 155 self.refresh() 156 157 def scroll_to(self, win, vt, hz=0, refresh=True): 158 line, col = self.pos[win] 159 160 hz_pos = hz 161 if hz_pos < 0: 162 hz_pos = 0 163 elif hz_pos >= 1000: 164 hz_pos = 999 165 166 vt_pos = vt 167 if vt_pos < 0: 168 vt_pos = 0 169 elif vt_pos >= 1000: 170 vt_pos = 1000 - 1 171 172 if line != vt_pos or col != hz_pos: 173 self.pos[win] = [vt_pos, hz_pos] 174 if refresh: 175 self.refresh() 176 177 def refresh(self): 178 for windex, (win, dim) in six.iteritems(self.windows): 179 h, w, sy, sx = dim 180 line, col = self.pos[windex] 181 if h is not None: 182 win.touchwin() 183 win.noutrefresh(line, col, sy+1, sx+1, sy+h-2, sx+w-2) 184 else: 185 win.noutrefresh() 186 curses.doupdate() 187 188 def write(self, text): 189 if six.PY3: 190 text = str(text) 191 else: 192 if isinstance(text, six.text_type): 193 #text = text.encode(self.stdout.encoding) 194 text = text.encode("UTF-8") 195 196 if NCURSES: 197 self.write_curses(text) 198 if self.logfile: 199 text = re.sub(self.TAG, "", text) 200 self.write_log(text) 201 else: 202 if GLOBALS["color_shell"]: 203 text = re.sub(self.TAG, shell_colorify_match, text) 204 else: 205 text = re.sub(self.TAG, "", text) 206 207 self.write_normal(text) 208 if self.logfile: 209 self.write_log(text) 210 211 def write_log(self, text): 212 self.logfile.write(text) 213 self.logfile.flush() 214 215 def write_normal(self, text): 216 #_text = '\n'.join(self.wrapper.wrap(text)) 217 #self.stdout.write(_text+"\n") 218 self.stdout.write(text) 219 220 def write_curses(self, text): 221 formatstr = deque() 222 for m in re.finditer(self.TAG, text): 223 x1, x2 = m.span() 224 cindex = safe_int(m.groups()[2]) 225 windex = safe_int(m.groups()[1]) 226 formatstr.append([x1, x2, cindex, windex]) 227 if not formatstr: 228 formatstr.append([None, 0, 1, 1]) 229 230 if formatstr[0][1] == 0: 231 stop, start, cindex, windex = formatstr.popleft() 232 if windex is None: 233 windex = 1 234 else: 235 stop, start, cindex, windex = None, 0, 1, 1 236 237 while start is not None: 238 if formatstr: 239 next_stop, next_start, next_cindex, next_windex = formatstr.popleft() 240 else: 241 next_stop, next_start, next_cindex, next_windex = None, None, cindex, windex 242 243 face = curses.color_pair(cindex) 244 win, (h, w, sy, sx) = self.windows[windex] 245 ln, cn = self.pos[windex] 246 # Is this too inefficient? 247 new_lines = text[start:next_stop].count("\n") 248 self.lines[windex] += new_lines 249 if self.lines[windex] > self.maxsize[windex]: 250 _y, _x = win.getyx() 251 252 for _i in self.lines[windex]-self.maxsize(windex): 253 win.move(0,0) 254 win.deleteln() 255 win.move(_y, _x) 256 257 # Visual scroll 258 if self.autoscroll[windex]: 259 scroll = self.lines[windex] - ln - h 260 if scroll > 0: 261 self.scroll(windex, scroll, refresh=False) 262 263 try: 264 win.addstr(text[start:next_stop], face) 265 except curses.error: 266 win.addstr("???") 267 268 start = next_start 269 stop = next_stop 270 cindex = next_cindex 271 if next_windex is not None: 272 windex = next_windex 273 274 self.refresh() 275 276 def resize_screen(self, s, frame): 277 278 import sys,fcntl,termios,struct 279 data = fcntl.ioctl(self.stdout.fileno(), termios.TIOCGWINSZ, '1234') 280 h, w = struct.unpack('hh', data) 281 282 win = self.windows 283 #main = curses.initscr() 284 #h, w = main.getmaxyx() 285 #win[0] = (main, (None, None, 0, 0)) 286 #curses.resizeterm(h, w) 287 288 win[0][0].resize(h, w) 289 win[0][0].clear() 290 info_win, error_win, debug_win = setup_layout(h, w) 291 win[1][1] = info_win 292 win[2][1] = error_win 293 win[3][1] = debug_win 294 self.refresh() 295 296def init_curses(main_scr): 297 if not NCURSES or not main_scr: 298 # curses disabled, no multi windows 299 return None 300 301 # Colors 302 curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) 303 curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) 304 curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK) 305 curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK) 306 curses.init_pair(5, curses.COLOR_RED, curses.COLOR_BLACK) 307 curses.init_pair(6, curses.COLOR_RED, curses.COLOR_BLACK) 308 curses.init_pair(10, curses.COLOR_WHITE, curses.COLOR_RED) 309 curses.init_pair(11, curses.COLOR_WHITE, curses.COLOR_YELLOW) 310 curses.init_pair(12, curses.COLOR_WHITE, curses.COLOR_MAGENTA) 311 312 WIN = {} 313 main = main_scr 314 h, w = main.getmaxyx() 315 WIN[0] = (main, (None, None, 0, 0)) 316 317 # Creates layout 318 info_win, error_win, debug_win = setup_layout(h, w) 319 320 WIN[1] = [curses.newpad(5000, 1000), info_win] 321 WIN[2] = [curses.newpad(5000, 1000), error_win] 322 WIN[3] = [curses.newpad(5000, 1000), debug_win] 323 324 325 #WIN[1], WIN[11] = newwin(h-1, w/2, 1,1) 326 #WIN[2], WIN[12] = newwin(h-dbg_h-1, (w/2)-1, 1, (w/2)+2) 327 #WIN[3], WIN[13] = newwin(dbg_h-1, (w/2)-1, h-dbg_h+1, (w/2)+2) 328 329 for windex, (w, dim) in six.iteritems(WIN): 330 #w = WIN[i] 331 #w.bkgd(str(windex)) 332 w.bkgd(" ") 333 w.keypad(1) 334 w.idlok(True) 335 w.scrollok(True) 336 return WIN 337 338def clear_env(): 339 try: 340 terminate_job_launcher() 341 except: 342 pass 343 344 base_dir = GLOBALS["basedir"] 345 lock_file = pjoin(base_dir, "alive") 346 try: 347 os.remove(lock_file) 348 except Exception: 349 print("could not remove lock file %s" %lock_file, file=sys.stderr) 350 351 clear_tempdir() 352 353def app_wrapper(func, args): 354 global NCURSES 355 base_dir = GLOBALS.get("scratch_dir", GLOBALS["basedir"]) 356 lock_file = pjoin(base_dir, "alive") 357 358 if not args.enable_ui: 359 NCURSES = False 360 361 if not pexist(lock_file) or args.clearall: 362 open(lock_file, "w").write(time.ctime()) 363 else: 364 clear_env() 365 print('\nThe same process seems to be running. Use --clearall or remove the lock file "alive" within the output dir', file=sys.stderr) 366 sys.exit(-1) 367 368 try: 369 if NCURSES: 370 curses.wrapper(main, func, args) 371 else: 372 main(None, func, args) 373 except ConfigError as e: 374 if GLOBALS.get('_background_scheduler', None): 375 GLOBALS['_background_scheduler'].terminate() 376 377 print("\nConfiguration Error:", e, file=sys.stderr) 378 clear_env() 379 sys.exit(-1) 380 except DataError as e: 381 if GLOBALS.get('_background_scheduler', None): 382 GLOBALS['_background_scheduler'].terminate() 383 384 print("\nData Error:", e, file=sys.stderr) 385 clear_env() 386 sys.exit(-1) 387 except KeyboardInterrupt: 388 # Control-C is also grabbed by the back_launcher, so it is no necessary 389 # to terminate from here 390 print("\nProgram was interrupted.", file=sys.stderr) 391 if args.monitor: 392 print(("VERY IMPORTANT !!!: Note that launched" 393 " jobs will keep running as you provided the --monitor flag"), file=sys.stderr) 394 clear_env() 395 sys.exit(-1) 396 except: 397 if GLOBALS.get('_background_scheduler', None): 398 GLOBALS['_background_scheduler'].terminate() 399 400 clear_env() 401 raise 402 else: 403 if GLOBALS.get('_background_scheduler', None): 404 GLOBALS['_background_scheduler'].terminate() 405 406 clear_env() 407 408 409def main(main_screen, func, args): 410 """ Init logging and Screen. Then call main function """ 411 global MAIN_LOG 412 # Do I use ncurses or basic terminal interface? 413 screen = Screen(init_curses(main_screen)) 414 415 # prints are handled by my Screen object 416 screen.stdout = sys.stdout 417 if args.logfile: 418 screen.logfile = open(os.path.join(GLOBALS["basedir"], "etebuild.log"), "w") 419 sys.stdout = screen 420 sys.stderr = screen 421 422 # Start logger, pointing to the selected screen 423 if not MAIN_LOG: 424 MAIN_LOG = True 425 log = get_main_log(screen, [28,26,24,22,20,10][args.verbosity]) 426 427 # Call main function as lower thread 428 if NCURSES: 429 screen.refresh() 430 exceptions = six.moves.queue.Queue() 431 t = ExcThread(bucket=exceptions, target=func, args=[args]) 432 t.daemon = True 433 t.start() 434 ln = 0 435 chars = "\\|/-\\|/-" 436 cbuff = 1 437 try: 438 while 1: 439 try: 440 exc = exceptions.get(block=False) 441 except six.moves.queue.Empty: 442 pass 443 else: 444 exc_type, exc_obj, exc_trace = exc 445 # deal with the exception 446 #print exc_trace, exc_type, exc_obj 447 raise exc_obj 448 449 mwin = screen.windows[0][0] 450 key = mwin.getch() 451 mwin.addstr(0, 0, "%s (%s) (%s) (%s)" %(key, screen.pos, ["%s %s" %(i,w[1]) for i,w in list(screen.windows.items())], screen.lines) + " "*50) 452 mwin.refresh() 453 if key == 113: 454 # Fixes the problem of prints without newline char 455 raise KeyboardInterrupt("Q Pressed") 456 if key == 9: 457 cbuff += 1 458 if cbuff>3: 459 cbuff = 1 460 elif key == curses.KEY_UP: 461 screen.scroll(cbuff, -1) 462 elif key == curses.KEY_DOWN: 463 screen.scroll(cbuff, 1) 464 elif key == curses.KEY_LEFT: 465 screen.scroll(cbuff, 0, -1) 466 elif key == curses.KEY_RIGHT: 467 screen.scroll(cbuff, 0, 1) 468 elif key == curses.KEY_NPAGE: 469 screen.scroll(cbuff, 10) 470 elif key == curses.KEY_PPAGE: 471 screen.scroll(cbuff, -10) 472 elif key == curses.KEY_END: 473 screen.scroll_to(cbuff, 999, 0) 474 elif key == curses.KEY_HOME: 475 screen.scroll_to(cbuff, 0, 0) 476 elif key == curses.KEY_RESIZE: 477 screen.resize_screen(None, None) 478 else: 479 pass 480 except: 481 # fixes the problem of restoring screen when last print 482 # did not contain a newline char. WTF! 483 print("\n") 484 raise 485 486 #while 1: 487 # if ln >= len(chars): 488 # ln = 0 489 # #screen.windows[0].addstr(0,0, chars[ln]) 490 # #screen.windows[0].refresh() 491 # time.sleep(0.2) 492 # ln += 1 493 else: 494 func(args) 495 496def setup_layout(h, w): 497 # Creates layout 498 header = 4 499 500 start_x = 0 501 start_y = header 502 h -= start_y 503 w -= start_x 504 505 h1 = h/2 + h%2 506 h2 = h/2 507 if w > 160: 508 # _______ 509 # | |___| 510 # |___|___| 511 w1 = w/2 + w%2 512 w2 = w/2 513 info_win = [h, w1, start_y, start_x] 514 error_win = [h1, w2, start_y, w1] 515 debug_win = [h2, w2, h1, w1] 516 else: 517 # ___ 518 # |___| 519 # |___| 520 # |___| 521 h2a = h2/2 + h2%2 522 h2b = h2/2 523 info_win = [h1, w, start_y, start_x] 524 error_win = [h2a, w, h1, start_x] 525 debug_win = [h2b, w, h1+h2a, start_x] 526 527 return info_win, error_win, debug_win 528 529 530