From c9082aee42918b83756b26c257cbce95e8683dad Mon Sep 17 00:00:00 2001 From: Bastian Kleineidam Date: Wed, 20 Jun 2012 22:33:26 +0200 Subject: [PATCH] Improved terminal functions. --- doc/changelog.txt | 1 + dosage | 16 +++--------- dosagelib/fileutil.py | 22 ++++++++++++++++ dosagelib/util.py | 58 ++++++++++++++++++++++++++++++------------- 4 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 dosagelib/fileutil.py diff --git a/doc/changelog.txt b/doc/changelog.txt index 63b1f1b91..f6ef5c78a 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -10,6 +10,7 @@ Changes: - comics: Removed the twisted and zope dependencies by adding an internal plugin search mechanism. - testing: Refactored the test comic routine in proper unit tests. +- cmdline: Improved terminal feature detection. Fixes: - comics: Adjusted Xkcd href values. diff --git a/dosage b/dosage index 16c270cdd..409933c54 100755 --- a/dosage +++ b/dosage @@ -22,7 +22,7 @@ import traceback from dosagelib import events, scraper from dosagelib.output import out -from dosagelib.util import getWindowSize, internal_error +from dosagelib.util import is_tty, get_columns, internal_error from dosagelib.configuration import App, Freeware, Copyright def setupOptions(): @@ -38,14 +38,7 @@ def setupOptions(): parser.add_option('-m', '--module-help', action='store_true', dest='modhelp', help='display help for comic modules') parser.add_option('-t', '--timestamps', action='store_true', dest='timestamps', default=False, help='print timestamps for all output at any info level') parser.add_option('-o', '--output', action='store', dest='output', choices=events.getHandlers(), help='output formatting for downloaded comics') - try: - getWindowSize() - except NotImplementedError: - progress = False - else: - progress = True - - if progress: + if is_tty(sys.stdout): parser.add_option('-p', '--progress', action='store_true', dest='progress', default=False, help='display progress bar while downloading comics') return parser @@ -131,10 +124,7 @@ class Dosage(object): print '\n'.join(scraper.get_name() for scraper in scrapers) def doColumnList(self, scrapers): - try: - screenWidth = getWindowSize() - except NotImplementedError: - screenWidth = 80 + screenWidth = get_columns() names = [scraper.get_name() for scraper in scrapers] maxlen = max([len(name) for name in names]) namesPerLine = int(screenWidth / (maxlen + 1)) diff --git a/dosagelib/fileutil.py b/dosagelib/fileutil.py new file mode 100644 index 000000000..121dd67b7 --- /dev/null +++ b/dosagelib/fileutil.py @@ -0,0 +1,22 @@ +# -*- coding: iso-8859-1 -*- +# Copyright (C) 2012 Bastian Kleineidam +""" +File and path utilities. +""" + +def has_module (name): + """Test if given module can be imported. + @return: flag if import is successful + @rtype: bool + """ + try: + exec "import %s as _bla" % name + return True + except (OSError, ImportError): + # some modules (for example HTMLtidy) raise OSError + return False + + +def is_tty (fp): + """Check if a file object is a TTY.""" + return (hasattr(fp, "isatty") and fp.isatty()) diff --git a/dosagelib/util.py b/dosagelib/util.py index 2b0318561..6d3654cab 100644 --- a/dosagelib/util.py +++ b/dosagelib/util.py @@ -2,8 +2,6 @@ from __future__ import division import urllib2, urlparse import sys -import struct -import array import os import cgi import re @@ -14,6 +12,12 @@ from math import log, floor from .output import out from .configuration import UserAgent, AppName, App, SupportUrl +from .fileutil import has_module, is_tty + +has_wconio = has_module("WConio") +has_curses = has_module("curses") +has_fcntl = has_module('fcntl') +has_termios = has_module('termios') class NoMatchError(Exception): pass @@ -148,21 +152,41 @@ def urlopen(url, referrer=None, retries=5): return urlobj -def getWindowSize(): - try: - from fcntl import ioctl - from termios import TIOCGWINSZ - except ImportError: - raise NotImplementedError - st = 'HHHH' - names = 'ws_row', 'ws_col', 'ws_xpixel', 'ws_ypixel' - buf = array.array('b', ' ' * struct.calcsize(st)) - try: - ioctl(sys.stderr, TIOCGWINSZ, buf, True) - except IOError: - raise NotImplementedError - winsize = dict(zip(names, struct.unpack(st, buf.tostring()))) - return winsize['ws_col'] + +def get_columns (fp): + """Return number of columns for given file.""" + if not is_tty(fp): + return 80 + if has_wconio: + import WConio + # gettextinfo() returns a tuple + # - left, top, right, bottom: window coordinates + # - textattr, normattr: current attributes + # - videomode: current video mode + # - height, width: screen size + # - curx, cury: current cursor position + # return the width: + return WConio.gettextinfo()[8] + if has_curses: + import curses + try: + curses.setupterm() + return curses.tigetnum("cols") + except curses.error: + pass + if has_fcntl and has_termios: + import fcntl, termios, array, struct + st = 'HHHH' + names = 'ws_row', 'ws_col', 'ws_xpixel', 'ws_ypixel' + buf = array.array('b', ' ' * struct.calcsize(st)) + try: + fcntl.ioctl(fp, termios.TIOCGWINSZ, buf, True) + winsize = dict(zip(names, struct.unpack(st, buf.tostring()))) + return winsize['ws_col'] + except IOError: + pass + return 80 + suffixes = ('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB')