Improve Python Wrapping Behavior

Discussion in 'Mac Programming' started by ArtOfWarfare, Jul 22, 2014.

  1. ArtOfWarfare, Jul 22, 2014
    Last edited: Jul 22, 2014

    ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #1
    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.
     
  2. ArtOfWarfare thread starter macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #3
    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.
     

Share This Page