Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

ArtOfWarfare

macrumors G3
Original poster
Nov 26, 2007
9,671
6,212
The default behavior of Python's output is if it has output which is too long, it wraps it exactly at the point where the output is too long. Often, it breaks in the middle of a word.

I wrote a method which modifies the behavior to prefer breaking on white space near the end of the line, although it'll wrap mid-non-writespace (blackspace? Is there a word for this?) if it has to.

I placed this my Python Startup file:

Code:
from string       import whitespace
from TerminalSize import getTerminalSize

# Save original sys.stdout.write implementation - it'll be replaced in a moment.
orgStdOutWrite = sys.stdout.write

class NewStdOut(object):
    def wrappedWrite(self, s, width):
        if not s:
            return

        # If there's enough space, just write whatever it is.
        if len(s) <= width:
            orgStdOutWrite(s)
            return

        i = 0
        # Scan forward to check for a linebreak before the end of the line.
        for c in s[:width]:
            if c == '\n':
                orgStdOutWrite(s[:i + 1])
                self.wrappedWrite(s[i + 1:], width)
                return
            i += 1

        i = width
        # Scan backwards for the whitespace nearest the end of the line.
        for c in s[width - 1::-1]:
            if c in whitespace:
                orgStdOutWrite(s[:i - 1] + '\n')
                self.wrappedWrite(s[i:], width)
                return
            i -= 1

        # If this is an unbroken sequence, then just add a break in the middle.
        orgStdOutWrite(s[:width] + '\n')
        self.wrappedWrite(s[width:], width)

    # Replace the standard write method:
    def write(self, s):
        self.wrappedWrite(s, getTerminalSize()[0])

# Replace sys.stdout with a new class which forwards from write to wrappedWrite
sys.stdout = NewStdOut()

This should mostly work except TerminalSize isn't a standard Python module (Python 3.4 includes similar functionality in a standard module, but I normally use 2.7).

Here's its contents:
Code:
"""
Taken from http://stackoverflow.com/a/6550596/901641
"""

def getTerminalSize():
   import platform
   current_os = platform.system()
   tuple_xy=None
   if current_os == 'Windows':
       tuple_xy = _getTerminalSize_windows()
       if tuple_xy is None:
          tuple_xy = _getTerminalSize_tput()
          # needed for window's python in cygwin's xterm!
   if current_os == 'Linux' or current_os == 'Darwin' or  current_os.startswith('CYGWIN'):
       tuple_xy = _getTerminalSize_linux()
   if tuple_xy is None:
       print "default"
       tuple_xy = (80, 25)      # default value
   return tuple_xy

def _getTerminalSize_windows():
    res=None
    try:
        from ctypes import windll, create_string_buffer

        # stdin handle is -10
        # stdout handle is -11
        # stderr handle is -12

        h = windll.kernel32.GetStdHandle(-12)
        csbi = create_string_buffer(22)
        res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
    except:
        return None
    if res:
        import struct
        (bufx, bufy, curx, cury, wattr,
         left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
        sizex = right - left + 1
        sizey = bottom - top + 1
        return sizex, sizey
    else:
        return None

def _getTerminalSize_tput():
    # get terminal width
    # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
    try:
       import subprocess
       proc=subprocess.Popen(["tput", "cols"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
       output=proc.communicate(input=None)
       cols=int(output[0])
       proc=subprocess.Popen(["tput", "lines"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
       output=proc.communicate(input=None)
       rows=int(output[0])
       return (cols,rows)
    except:
       return None


def _getTerminalSize_linux():
    def ioctl_GWINSZ(fd):
        try:
            import fcntl, termios, struct, os
            cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'1234'))
        except:
            return None
        return cr
    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
    if not cr:
        try:
            fd = os.open(os.ctermid(), os.O_RDONLY)
            cr = ioctl_GWINSZ(fd)
            os.close(fd)
        except:
            pass
    if not cr:
        try:
            cr = (env['LINES'], env['COLUMNS'])
        except:
            return None
    return int(cr[1]), int(cr[0])

I don't have any questions (although feel free to suggest improvements to the code). I just felt that this was too good to not be shared with anyone else who uses Python. I'd wrap it up and put it on PIP but it would seem a bit odd there given it's more a script that modifies Python than a module that adds-on to Python, if that makes any sense.
 
Last edited:

Damn standard library has everything you could ever need xD.

In any event, that only changes my code down to this:

Code:
from TerminalSize import getTerminalSize
from textwrap    import fill

# Save original sys.stdout.write implementation - it'll be replaced in a moment.
orgStdOutWrite = sys.stdout.write

class NewStdOut(object):
    def write(self, s):
       orgStdOutWrite(fill(s, getTerminalSize()[0]))

# Replace sys.stdout with a new class which wraps text before outputting it.
sys.stdout = NewStdOut()

That library doesn't handle the size of the terminal on its own, and it doesn't replace sys.stdout.write with itself. So my code is still somewhat useful if not as useful as I thought it would be.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.