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
38 changes: 34 additions & 4 deletions icecream/icecream.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import ast
import inspect
import pprint
import os
import sys
from datetime import datetime
from contextlib import contextmanager
Expand Down Expand Up @@ -87,6 +88,7 @@ def colorizedStderrPrint(s):
DEFAULT_CONTEXT_DELIMITER = '- '
DEFAULT_OUTPUT_FUNCTION = colorizedStderrPrint
DEFAULT_ARG_TO_STRING_FUNCTION = pprint.pformat
COLUMN_OVERFLOW = 4 # Line length appears to overflow by 4 characters.


class NoSourceAvailableError(OSError):
Expand Down Expand Up @@ -154,15 +156,40 @@ def format_pair(prefix, arg, value):
return '\n'.join(lines)


def argumentToString(obj):
s = DEFAULT_ARG_TO_STRING_FUNCTION(obj)
# def argumentToString(obj, width):
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 columns():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please give this a more descriptive name.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

""" Returns the number of columns that this terminal can handle. """
width = DEFAULT_LINE_WRAP_WIDTH
try:
# TODO come up with a more elegant solution than subtracting
# a seemingly random number of characters.
Copy link
Author

Choose a reason for hiding this comment

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

Any thoughts about this overflow thingy? I just realised that the extra characters might come from the ic | prefix.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's probably what it is, yes. Have this function return the full width, then deal with the length of the prefix inside the class. Remember there's both the ic | prefix (which can be configured) as well as the argPrefix.

Copy link
Author

Choose a reason for hiding this comment

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

Let me know what you think of my new solution.

width = os.get_terminal_size().columns - COLUMN_OVERFLOW
Copy link
Collaborator

Choose a reason for hiding this comment

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

The docs https://docs.python.org/3/library/os.html#os.get_terminal_size suggest using shutil (https://docs.python.org/3/library/shutil.html#shutil.get_terminal_size) instead, and looking at that function I think that's a good idea.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

except OSError: # Not in TTY
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like the shutil version doesn't raise OSError any more. But maybe the whole thing should be wrapped in except Exception just in case something weird happens.

Also you should pass the default width as a fallback to get_terminal_size. Otherwise it's going to use its own default fallback (80 columns) when the other methods fail.

Copy link
Author

Choose a reason for hiding this comment

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

Will do on both counts.

pass
except AttributeError: # Python 2.x
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks a bit too broad and prone to hiding real errors. I'd prefer either checking the Python version or checking for the presence of the relevant attribute.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

width = os.environ.get('COLUMNS', DEFAULT_LINE_WRAP_WIDTH)
return width


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
lineWrapWidth = columns()
contextDelimiter = DEFAULT_CONTEXT_DELIMITER

def __init__(self, prefix=DEFAULT_PREFIX,
Expand All @@ -173,6 +200,7 @@ def __init__(self, prefix=DEFAULT_PREFIX,
self.includeContext = includeContext
self.outputFunction = outputFunction
self.argToStringFunction = argToStringFunction
self.passWidthParam = supports_param(self.argToStringFunction)

def __call__(self, *args):
if self.enabled:
Expand Down Expand Up @@ -232,7 +260,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 +354,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
11 changes: 11 additions & 0 deletions tests/test_icecream.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ def testSingleTupleArgument(self):
self.assertEqual(pair, ('(a, b)', '(1, 2)'))

def testMultilineContainerArgs(self):
ic.lineWrapWidth = icecream.DEFAULT_LINE_WRAP_WIDTH
dawngerpony marked this conversation as resolved.
Show resolved Hide resolved
with disableColoring(), captureStandardStreams() as (out, err):
ic((a,
b))
Expand Down Expand Up @@ -518,3 +519,13 @@ def testColoring(self):
ic({1: 'str'}) # Output should be colored with ANSI control codes.

assert hasAnsiEscapeCodes(err.getvalue())

@unittest.skipIf(int(sys.version[0]) == 2, "pprint doesn't wrap long strings in Python 2.7")
def testLineLengthTen(self):
""" Test a specific line wrap width. """
ic.lineWrapWidth = 10
s = "123456789 1234567890"
with disableColoring(), captureStandardStreams() as (out, err):
ic(s)
pair = parseOutputIntoPairs(out, err, 2)[0]
self.assertEqual(pair, [('s', "('123456789 '\n '1234567890')")])
dawngerpony marked this conversation as resolved.
Show resolved Hide resolved