Changeset 280

Show
Ignore:
Timestamp:
11/23/05 18:43:58 (3 years ago)
Author:
athomas
Message:
  • Added console.cwraptext(), replacing the use of the built-in textwrap module.
  • Added console.rjustify() and cjustify().
  • console.termwidth() now attempts to use curses to obtain the terminal width.
  • Added console.termheight().
  • console.cprint() can optionally take a stream as its first argument.
  • console.error() and warning() now output to sys.stderr
  • console.print_table() has been completely rewritten and now supports wrapping within columns.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • pycrash/trunk/crash/console.py

    r276 r280  
    11import re, sys, os 
    2 from crash.util import textwrap, if_, isnumber 
    3  
    4 __cprint_re = re.compile(r'[^^]+|\^([N0-7BU])') 
    5 __cprint_strip = re.compile('\^([N0-7BU])') 
     2from crash.util import if_, isnumber 
     3 
     4__cprint_re = re.compile(r'''[^^]+|\^([N0-7BU])''') 
     5__cprint_strip = re.compile(r'''\^([N0-7BU])''') 
    66__colour_terminal = 0 
    77 
     
    1111    if curses.tigetnum('colors') >= 0: 
    1212        __colour_terminal = 1 
    13 except ImportError
     13except
    1414    pass 
    1515 
    1616if sys.stdout.isatty() and __colour_terminal: 
    17     def cwrite(io, *args): 
    18         """ Print using colour escape codes similar to the Quake engine. That is, 
    19         ^0-7 correspond to colours, ^B toggles bold, ^U toggles underline and 
    20         ^N is reset to normal text. Colour is not automatically reset at the 
    21         end of output. """ 
    22         text = ' '.join(map(str, args)) 
     17    def cwrite(io, text): 
    2318        bold = underline = 0 
    2419        out = [] 
     
    4742            else: 
    4843                out.append(token.group(0)) 
    49         io.write(''.join(out)) 
     44        for t in out: 
     45            io.write(t) 
    5046 
    5147else: 
    52     def cwrite(io, *args): 
    53         io.write(__cprint_strip.sub('', ' '.join(map(str, args)))) 
     48    def cwrite(io, text): 
     49        io.write(__cprint_strip.sub('', text)) 
    5450         
     51cwrite.__doc__ = """ Print using colour escape codes similar to the Quake engine. That is, 
     52        ^0-7 correspond to colours, ^B toggles bold, ^U toggles underline and 
     53        ^N is reset to normal text. Colour is not automatically reset at the 
     54        end of output. """ 
     55 
    5556def cprint(*args): 
    56     cwrite(sys.stdout, *(args + ('\n',))) 
     57    stream = sys.stdout 
     58    if args and type(args[0]) is file: 
     59        stream = args[0] 
     60        args = args[1:] 
     61    cwrite(stream, ' '.join(args) + '\n') 
    5762 
    5863def cprintstrip(*args): 
     
    6469def error(*args): 
    6570    """ Print a red error message prefixed by ERR. """ 
    66     cprint("^1^BERR " + ' '.join(map(str, args)) + '^N') 
     71    cprint(sys.stderr, "^1^BERR " + ' '.join(map(str, args)) + '^N') 
    6772 
    6873def fatal(*args): 
    6974    """ Print a red error message prefixed by FTL, then exit with status -1 """ 
    70     cprint("^1^BFTL " + ' '.join(map(str, args)) + '^N') 
     75    cprint(sys.stderr, "^1^BFTL " + ' '.join(map(str, args)) + '^N') 
    7176    sys.exit(-1) 
    7277 
    7378def warning(*args): 
    7479    """ Print a yellow warning message prefixed by WRN """ 
    75     cprint("^3^BWRN " + ' '.join(map(str, args)) + '^N') 
     80    cprint(sys.stderr, "^3^BWRN " + ' '.join(map(str, args)) + '^N') 
    7681 
    7782def info(*args): 
     
    8085 
    8186def termwidth(): 
    82     """ Find the current terminal width """ 
    83     return 'COLUMNS' in os.environ and int(os.environ['COLUMNS']) or 80 
    84  
    85 def wraptoterm(text, **argd): 
     87    """ Guess the current terminal width. """ 
     88    try: 
     89        return curses.tigetnum('cols') 
     90    except: 
     91        return 'COLUMNS' in os.environ and int(os.environ['COLUMNS']) or 80 
     92 
     93def termheight(): 
     94    """ Guess the current terminal height. """ 
     95    try: 
     96        return curses.tigetnum('lines') 
     97    except: 
     98        return 'LINES' in os.environ and int(os.environ['LINES']) or 25 
     99 
     100def csplice(text, start = 0, end = -1): 
     101    """ Splice a colour encoded string. """ 
     102    out = '' 
     103    if end == -1: 
     104        end = len(text) 
     105    sofar = 0 
     106    for token in __cprint_re.finditer(text): 
     107        if sofar > end: break 
     108        txt = token.group(0) 
     109        if token.group(1): 
     110            if start < sofar < end: 
     111                out += txt 
     112        else: 
     113            # Whether beginning and end of segment are in slice 
     114            bs = start < sofar < end 
     115            es = start < sofar + len(txt) < end 
     116            if bs and es: 
     117                out += txt 
     118            elif not bs and es: 
     119                out += txt[start - sofar:] 
     120            elif bs and not es: 
     121                out += txt[:end - sofar] 
     122                break 
     123            elif sofar <= start and sofar + len(txt) >= end: 
     124                out += txt[start - sofar:end] 
     125                break 
     126            sofar += len(txt) 
     127    return out 
     128 
     129__cwrap_re = re.compile(r'''(\n)|(\s+)|((?:\^[N0-7BU]|\S)+\b[^^\w]*)''') 
     130def cwraptext(text, width = termwidth(), subsequent_indent = ''): 
     131    """ Wrap multi-line text to width (defaults to termwidth()) """ 
     132    out = [] 
     133    tokens = [t.group(0) for t in __cwrap_re.finditer(text)] + [' ' * width] 
     134    line = tokens.pop(0) 
     135    first_line = 1 
     136 
     137 
     138    def add_line(line, first_line): 
     139        if clen(line.rstrip()) > width: 
     140            tokens.insert(0, csplice(line, width)) 
     141            line = csplice(line, 0, width) 
     142        out.append((not first_line and subsequent_indent or '') + line.rstrip()) 
     143        first_line = 0 
     144        if not out[-1]: 
     145            out.pop() 
     146        return first_line 
     147 
     148    while tokens: 
     149        if clen(line) + clen(tokens[0].rstrip()) > width: 
     150            first_line = add_line(line, first_line) 
     151            line = tokens.pop(0) 
     152        else: 
     153            line += tokens.pop(0) 
     154    if line: 
     155        add_line(line, first_line) 
     156    return out 
     157 
     158def wraptoterm(text, **kwargs): 
    86159    """ Wrap the given text to the current terminal width """ 
    87     return '\n'.join(textwrap.wrap(text, termwidth(), **argd)) 
    88  
    89 def print_table(table, header=None, sep=' ', numfmt='%g', auto_format = True): 
     160    return '\n'.join(cwraptext(text, **kwargs)) 
     161 
     162def rjustify(text, width = termwidth()): 
     163    """ Right justify the given text. """ 
     164    text = cwraptext(text, width) 
     165    out = '' 
     166    for line in text: 
     167        out += (' ' * (width - clen(line))) + line + '\n' 
     168    return out.rstrip() 
     169 
     170def cjustify(text, width = termwidth()): 
     171    """ Centre the given text. """ 
     172    text = cwraptext(text, width) 
     173    out = '' 
     174    for line in text: 
     175        out += (' ' * ((width - clen(line)) / 2)) + line + '\n' 
     176    return out.rstrip() 
     177 
     178def print_table(table, header = None, sep = ' ', auto_format = ['^B^U', '^6', '^B^6']): 
    90179    """Print a list of lists as a table, so that columns line up nicely. 
    91     header, if specified, will be printed as the first row. 
    92     numfmt is the format for all numbers; you might want e.g. '%6.2f'. 
    93     (If you want different formats in different columns, don't use 
    94     print_table.) sep is the separator between columns. 
     180    header, if specified, will be printed as the first row. sep is the 
     181    separator between columns. 
    95182     
    96183    auto_format is a list specifying the formatting colours to use for each 
    97184    row. The first element is the header colour, subsequent elements are 
    98     for alternating rows. eg. [ '^B', '^1', '^2' ] """ 
    99     if not table and not header: 
    100         return 
    101     def ljust(s, size): 
    102         return s + ' ' * (size - clen(s)) 
    103     def rjust(s, size): 
    104         return ' ' * (size - clen(s)) + s 
    105     if auto_format and type(auto_format) is not list: 
    106         auto_format = ['^B^U', '^6', '^B^6'] 
     185    for alternating rows. eg. [ '^B', '^1', '^2' ] 
     186     
     187    Note: print_table supports an additional formatting code, ^R, which 
     188    corresponds to the colour formatting of the current table row. """ 
     189    def ctlen(s): 
     190        return clen(s.replace('^R', '')) 
     191 
     192    # Find column scale factor 
    107193    if header: 
    108         table = [header] + table 
    109     justs = [isnumber(x) and rjust or ljust for x in table[0]] 
    110     table = [[if_(isnumber(x), lambda: numfmt % x, x)  for x in row] 
    111              for row in table] 
    112     maxlen = lambda seq: max(map(clen, seq)) 
    113     sizes = map(maxlen, zip(*[map(str, row) for row in table])) 
     194        table.insert(0, header) 
     195    rows, cols = len(table), len(table[0]) 
     196    colwidths = [0] * cols 
     197    #minwidth = termwidth() / cols 
     198    for i in range(0, cols): 
     199        colwidths[i] = max(map(lambda c: max(map(ctlen, c[i].splitlines())), table)) 
     200        if i < cols - 1: 
     201            colwidths[i] += len(sep) 
     202    twidth = sum(colwidths) 
     203    scale = float(termwidth() - 1) / float(twidth) 
     204    colwidths = [int(float(x) * scale) for x in colwidths] 
     205    auto_format = auto_format[:] 
     206 
    114207    rowalt = -1 
     208 
     209    if not header: 
     210        auto_format = auto_format[1:] 
     211        rowalt = 0 
     212 
    115213    for row in table: 
    116214        fmt = '' 
    117         if auto_format: 
    118             if rowalt == -1: 
    119                 fmt = auto_format[0] 
    120                 auto_format.pop(0) 
    121             else: 
    122                 fmt = auto_format[rowalt % len(auto_format)] 
    123         cwrite(sys.stdout, fmt) 
    124         for (j, size, x) in zip(justs, sizes, row): 
    125             cwrite(sys.stdout, j(x, size), sep) 
    126         cprint('^N') 
     215        if rowalt == -1: 
     216            fmt = auto_format[0] 
     217            auto_format.pop(0) 
     218        else: 
     219            fmt = auto_format[rowalt % len(auto_format)] 
     220        xrow = [cwraptext(col.replace('^R', fmt), colwidths[i]) for i, col in enumerate(row)] 
     221        realrows = max(map(len, xrow)) 
     222        xrow = [x + [''] * (realrows - len(x)) for x in xrow] 
     223        for i in range(0, realrows): 
     224            cwrite(sys.stdout, fmt) 
     225            for j, col in enumerate(xrow): 
     226                cwrite(sys.stdout, col[i].replace('^R', fmt) + sep * (colwidths[j] - ctlen(col[i]))) 
     227                if j < cols - 1: 
     228                    cwrite(sys.stdout, sep) 
     229            cwrite(sys.stdout, '^N\n') 
    127230        rowalt += 1