# $Id: condensedPrint.py 081917d30609 2010-03-05 mtnyogi $
# coding=utf-8
# 
# Copyright © 2007-2008 Bruce Frederiksen
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import sys
import types
import re

def cprint(obj, maxlen = 80, maxdepth = 4, maxlines = 20):
    items = cprint2(obj, maxdepth)
    #sys.stderr.write("cprint items: %s\n" % str(items))
    return format(items, maxlen, maxlen, maxlines)[0]

def format_len(x):
    """
        >>> format_len('abc')
        3
        >>> format_len(('(', ('(', 'def', ')'), 'yz', ')'))
        11
    """
    if not isinstance(x, (list, tuple)): return len(x)
    if len(x) > 3: sep_len = 2 * (len(x) - 3)
    else: sep_len = 0
    return sum(map(format_len, x)) + sep_len

def format(x, lenleft, maxlen, maxlines, indent = 0):
    r"""
        >>> format('"hello mom this is a long str"', 7, 80, 9)
        ('"he..."', 0)
        >>> format(('(', 'a', 'b', 'c', ')'), 80, 80, 9)
        ('(a, b, c)', 0)
        >>> format(('(', 'a', 'b', 'c', ')'), 8, 80, 9)
        ('(a,\n  b,\n  c)', 2)
    """
    if not isinstance(x, (list, tuple)):
        if len(x) <= lenleft: return x, 0
        if isinstance(x, str) and x[-1] in "'\"":
            if lenleft >= 5: return x[:lenleft-4] + '...' + x[-1], 0
        else:
            if lenleft >= 4: return x[:lenleft-3] + '...', 0
        return '&', 0
    if len(x) == 0: return '', 0
    if format_len(x) <= lenleft:
        return x[0] + \
               ', '.join(format(y, lenleft, maxlen, maxlines)[0]
                           for y in x[1:-1]) + \
               x[-1], 0
    indent += 2
    ans = x[0]
    lines_taken = 0
    if len(x) > 2:
        first, taken = \
            format(x[1], lenleft - len(ans), maxlen, maxlines, indent + 2)
        ans += first
        lines_taken += taken
        for y in x[2:-1]:
            if lines_taken >= maxlines:
                ans += ', ...'
                break
            line, taken = \
                format(y, maxlen - indent, maxlen, maxlines - lines_taken,
                       indent)
            ans += ',\n' + indent * ' ' + line
            lines_taken += taken + 1
    return ans + x[-1], lines_taken

def cprint2(obj, maxdepth):
    if isinstance(obj, tuple):
        return printSeq('(', ')', obj, maxdepth)
    if isinstance(obj, list):
        return printSeq('[', ']', obj, maxdepth)
    if isinstance(obj, dict):
        return printDict(obj, maxdepth)
    if isinstance(obj, str):
        return printStr(obj)
    try:
        return str(obj)
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        import traceback
        if isinstance(obj, types.InstanceType): obj_type = obj.__class__
        else: obj_type = type(obj)
        return "While trying to cprint a %s, got: %s" % \
                   (obj_type,
                    traceback.format_exception_only(exc_type, exc_value))

str_chk = re.compile('[a-zA-Z_][a-zA-Z0-9_]*$')

def printStr(str):
    """
        >>> printStr('hello_34_A')
        'hello_34_A'
        >>> printStr('hello 34_A')
        "'hello 34_A'"
    """
    if str_chk.match(str): return str
    return repr(str)

def printSeq(startChar, endChar, seq, maxdepth):
    """
        >>> printSeq('(', ')', (1, 2, 3), 4)
        ['(', '1', '2', '3', ')']
        >>> printSeq('(', ')', (), 4)
        ['(', ')']
    """
    if maxdepth < 1: return '&'
    maxdepth -= 1
    return [startChar] + [cprint2(x, maxdepth) for x in seq] + [endChar]

def item(key, value, maxdepth, separator):
    """
        >>> item('hello', 'bob', 3, '=')
        'hello=bob'
        >>> item(('hello', 'there'), 'bob', 3, '=')
        ['(', 'hello', 'there', ')=bob']
        >>> item('hello', ('extra', 'bob'), 3, '=')
        ['hello=(', 'extra', 'bob', ')']
        >>> item(('hello', 'there'), ('extra', 'bob'), 3, '=')
        ['(', 'hello', 'there', ')=(', 'extra', 'bob', ')']
    """
    keyans = cprint2(key, maxdepth)
    valans = cprint2(value, maxdepth)
    if isinstance(keyans, list):
        keyans[-1] += separator
        if isinstance(valans, list):
            keyans[-1] += valans[0]
            keyans.extend(valans[1:])
        else:
            keyans[-1] += valans
        return keyans
    if isinstance(valans, list):
        valans[0] = keyans + separator + valans[0]
        return valans
    return keyans + separator + valans

def printDict(dict, maxdepth,
                    startChar = '{', endChar = '}', separator = ': '):
    """
        >>> printDict({1:2, 3:4, 5:(6,7)}, 5)
        ['{', '1: 2', '3: 4', ['5: (', '6', '7', ')'], '}']
        >>> printDict({}, 5)
        ['{', '}']
    """
    if maxdepth < 1: return '&'
    maxdepth -= 1
    keys = list(dict.keys())
    keys.sort()
    return [startChar] + \
           [item(key, dict[key], maxdepth, separator) for key in keys] + \
           [endChar]

