Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial version of auto-detection of terminal width. #110

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
45 changes: 41 additions & 4 deletions icecream/icecream.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import ast
import inspect
import os
import pprint
import sys
from datetime import datetime
Expand All @@ -33,6 +34,15 @@

from .coloring import SolarizedDark

try:
from shutil import get_terminal_size
except ImportError:
try:
from backports.shutil_get_terminal_size import get_terminal_size
except ImportError:
def get_terminal_size():
return os.environ['COLUMNS']


PYTHON2 = (sys.version_info[0] == 2)

Expand Down Expand Up @@ -154,15 +164,38 @@ def format_pair(prefix, arg, value):
return '\n'.join(lines)


def argumentToString(obj):
s = DEFAULT_ARG_TO_STRING_FUNCTION(obj)
def argumentToString(obj, width=DEFAULT_LINE_WRAP_WIDTH):
s = DEFAULT_ARG_TO_STRING_FUNCTION(obj, width=width)
s = s.replace('\\n', '\n') # Preserve string newlines in output.
return s


def detect_terminal_width(prefix, default=DEFAULT_LINE_WRAP_WIDTH):
""" Returns the number of columns that this terminal can handle. """
width = default
try:
# We need to pass a terminal height in the tuple so we pass the default
# of 25 lines but it's not used for anything.
width = get_terminal_size((default, 25)).columns
except Exception: # Not in TTY or something else went wrong
pass
dawngerpony marked this conversation as resolved.
Show resolved Hide resolved
# TODO account for argPrefix()
# TODO make sure we support configureOutput()
return width - len(prefix)


def supports_param(fn, param="width"):
""" Returns True if the function supports that parameter. """
try:
from inspect import signature
return param in signature(fn).parameters
except ImportError: # Python 2.x
from inspect import getargspec
return param in getargspec(fn).args


class IceCreamDebugger:
_pairDelimiter = ', ' # Used by the tests in tests/.
lineWrapWidth = DEFAULT_LINE_WRAP_WIDTH
contextDelimiter = DEFAULT_CONTEXT_DELIMITER

def __init__(self, prefix=DEFAULT_PREFIX,
Expand All @@ -173,6 +206,8 @@ def __init__(self, prefix=DEFAULT_PREFIX,
self.includeContext = includeContext
self.outputFunction = outputFunction
self.argToStringFunction = argToStringFunction
self.passWidthParam = supports_param(self.argToStringFunction)
self.lineWrapWidth = detect_terminal_width(self.prefix, DEFAULT_LINE_WRAP_WIDTH)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still doesn't account for the width of argPrefix, or changing the prefix with configureOutput.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will fix.


def __call__(self, *args):
if self.enabled:
Expand Down Expand Up @@ -232,7 +267,8 @@ def _constructArgumentOutput(self, prefix, context, pairs):
def argPrefix(arg):
return '%s: ' % arg

pairs = [(arg, self.argToStringFunction(val)) for arg, val in pairs]
kwargs = {"width": self.lineWrapWidth} if self.passWidthParam else {}
pairs = [(arg, self.argToStringFunction(val, **kwargs)) for arg, val in pairs]
# For cleaner output, if <arg> is a literal, eg 3, "string", b'bytes',
# etc, only output the value, not the argument and the value, as the
# argument and the value will be identical or nigh identical. Ex: with
Expand Down Expand Up @@ -325,6 +361,7 @@ def configureOutput(self, prefix=_absent, outputFunction=_absent,

if argToStringFunction is not _absent:
self.argToStringFunction = argToStringFunction
self.passWidthParam = supports_param(self.argToStringFunction)

if includeContext is not _absent:
self.includeContext = includeContext
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def run_tests(self):
'pygments>=2.2.0',
'executing>=0.3.1',
'asttokens>=2.0.1',
'backports.shutil-get-terminal-size==1.0.0; python_version < "3.3.0"',
],
cmdclass={
'test': RunTests,
Expand Down
30 changes: 30 additions & 0 deletions tests/test_icecream.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#
# License: MIT
#
import textwrap

import sys
import unittest
Expand Down Expand Up @@ -182,6 +183,7 @@ def parseOutputIntoPairs(out, err, assertNumLines,
class TestIceCream(unittest.TestCase):
def setUp(self):
ic._pairDelimiter = TEST_PAIR_DELIMITER
ic.lineWrapWidth = icecream.DEFAULT_LINE_WRAP_WIDTH

def testWithoutArgs(self):
with disableColoring(), captureStandardStreams() as (out, err):
Expand Down Expand Up @@ -518,3 +520,31 @@ def testColoring(self):
ic({1: 'str'}) # Output should be colored with ANSI control codes.

assert hasAnsiEscapeCodes(err.getvalue())

def testStringWithShortLineWrapWidth(self):
""" Test a string with a short line wrap width. """
ic.lineWrapWidth = 10
s = "123456789 1234567890"
with disableColoring(), captureStandardStreams() as (out, err):
ic(s)
if icecream.PYTHON2:
expected = "ic| s: '123456789 1234567890'"
else:
expected = textwrap.dedent("""
ic| s: ('123456789 '
'1234567890')
""").strip()
self.assertEqual(err.getvalue().strip(), expected)

def testListWithShortLineWrapWidth(self):
""" Test a list with a short line wrap width. """
ic.lineWrapWidth = 10
lst = ["1", "2", "3", "4"]
with disableColoring(), captureStandardStreams() as (out, err):
ic(lst)
expected = textwrap.dedent("""
ic| lst: ['1',
'2',
'3',
'4']""").strip()
self.assertEqual(err.getvalue().strip(), expected)