Python Forum
colpage.py - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: General (https://python-forum.io/forum-1.html)
+--- Forum: Code sharing (https://python-forum.io/forum-5.html)
+--- Thread: colpage.py (/thread-3104.html)

Pages: 1 2


colpage.py - Skaperen - Apr-29-2017

colpage.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
"""Organize strings into columns and columns into pages using separate coroutines.

file          colpage.py
purpose       organize strings into columns and columns into pages
email         10054452614123394844460370234029112340408691

The intent is that this command works correctly under both Python 2 and
Python 3.  Please report failures or code improvement to the author.
"""

__license__ = """
Copyright (C) 2017, by Phil D. Howard - all other rights reserved

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA, OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

The author may be contacted by decoding the number
10054452614123394844460370234029112340408691
(provu igi la numeron al duuma)
"""

import fcntl, os, struct, sys, termios


def get_terminal_geometry(fd=1):
    """Get terminal geometry (width,heght) for a specified fd."""
    import fcntl,os,struct,termios
    try:
        fd = os.open('/dev/tty',os.O_WRONLY)
        r = struct.unpack('4H',fcntl.ioctl(fd,termios.TIOCGWINSZ,struct.pack('4H',0,0,0,0)))[1::-1]
        os.close(fd)
    except:
        r = None
    return r


class colpage:
    """class representing column formatted pages"""


    def __init__(self,gutter=None,width=None,height=None):
        """initialize class colpage"""

        self.page_num = 0

        return self.geometry( gutter=gutter, width=width, height=height )


    def flush(self):
        """flush pending columns to a completed page"""

        gutter = self.gutter
        page = ['']*self.height
        mc = len(self.columns)-1
        for n,column in enumerate(self.columns):    # add each column to the page on the right side
            width = self.widths[n]
            for m,line in enumerate(column):        # append each line to the page being built
                page[m] += (line+' '*width)[:width]
                if n < mc:                          # append a gutter except after the last column
                    page[m] += ' '*gutter
        self.pages.append(page)                     # add the new page to the list of completed pages
        self.columns = []
        self.widths = []
        return len(self.pages)


    def geometry(self,gutter=None,width=None,height=None):
        """set or get colpage geometry (gutter,width,height)"""

        if 'gutter' in dir(self) and gutter is None and width is None and height is None:
            self_gutter=self.gutter
            return (self.gutter,self.width,self.height)

        if width is None or height is None:
            term = get_terminal_geometry()

        if isinstance(gutter,int):
            self.gutter = gutter
        elif gutter is None:
            gutter = 1
            if 'gutter' in dir(self):
                self.gutter = gutter

        if isinstance(width,int):
            self.width = width
        if width is None:
            width = term[0]
            if 'width' in dir(self):
                self.width = width

        if isinstance(height,int):
            self.height = height
        if height is None:
            height = term[1]
            if 'height' in dir(self):
                self.height = height

        if gutter < 0:
            raise ValueError

        if width < 2:
            raise ValueError

        if height < 2:
            raise ValueError

        self.gutter = gutter
        self.height = height
        self.width = width

        self.columns = []
        self.lines = []
        self.pages = []
        self.widths = []

        self.page_starter = ['']*self.height
    
        return


    def destruct(self):
        del self
        return


    def get(self):
        """get the next completed page (list of list of str) or None."""
        
        if len(self.pages) > 0:
            return self.pages.pop(0)
        return None


    def print_ready_pages(self,**opts):
        """print the pages that are ready."""

        while True:
            page = self.get()
            if page == None:
                break
            for line in page:
                print(line,**opts)
        return



    def put(self,*args):
        """put a new string as a line into a page column."""

        if len(args) > 0:

            line = ' '.join(args)
            self.lines.append(line)

            # if this new line does not fill the height, then we are done
            if len(self.lines) < self.height:
                return

        # the working list is full or being flushed by put() with no arguments

        width = max(len(l) for l in self.lines)
        total_width = sum(this_width+self.gutter for this_width in self.widths)+width
        if total_width >= self.width:
            self.flush()

        self.columns.append(self.lines)
        self.widths.append(width)

        if len(args) > 0:
            self.lines = []
        else:
            self.flush()

        return


def main(args):
    """main: command line reads stdin, forms it into columns, outputs to stdout."""

    if len(args) > 3:
        gutter, width, height = [int(arg) for arg in args[1:]]
        colz = colpage( gutter=gutter, width=width, height=height )
    else:
        # let colpage try to get the geometry from the terminal size
        colz = colpage( gutter=1 )

    # feed stdin to colpage, send its pages to stdout
    for line in sys.stdin:
        colz.put(line[:-1])
        colz.print_ready_pages()

    # finish up at EOF
    colz.put()
    colz.print_ready_pages()

    return


if __name__ == '__main__':
    try:
        result=main(sys.argv)
        sys.stdout.flush()
    except BrokenPipeError:
        result=99
    except KeyboardInterrupt:
        print('')
        result=98
    if result is 0 or result is None or result is True:
        exit(0)
    if result is 1 or result is False:
        exit(1)
    if isinstance(result,str):
        print(result,file=sys.stderr)
        exit(2)
    try:
        exit(int(result))
    except ValueError:
        print(str(result),file=sys.stderr)
        exit(3)
    except TypeError:
        exit(4)
# EOF
this is a script that implements a class and command to format a one-at-a-time sequence (the caller put()s each line one at a time with an empty put() call to indicate the end of the sequence of lines) into column oriented pages (the caller checks for a finished page via get() or print_ready_pages()).  the command works on stdin and stdout.  the geometry (3 numbers: gutter, width, height) is optional and it tries to use the controlling terminal geometry if not given. the order of forming the columns is top to bottom, on the left column first, adding each column on the right side until no more space is available for the width.

my thoughts for the future:
1. a generator version.
2. a means to have it print each finished page
3. headers/footers/sidings on each page, maybe with page numbers.


RE: colpage.py - volcano63 - Apr-29-2017

__init__ does not return a value!!!!
It is not a constructor - as some imply, it is called by a constructor - which is __new__


RE: colpage.py - nilamo - Apr-29-2017

It returns self.geometry(), which itself doesn't return anything. So it looks like it's just doing more setup, but has return there to... confuse you? :p


RE: colpage.py - Skaperen - Apr-30-2017

what should it have? it does work, but maybe that won't be the case in a future release.


RE: colpage.py - Skaperen - Apr-30-2017

it has been my understanding that return is the same as return None.


RE: colpage.py - nilamo - Apr-30-2017

That's not what you're doing, though.
class colpage:
    def __init__(self,gutter=None,width=None,height=None):
        return self.geometry( gutter=gutter, width=width, height=height )
It's just weird, not wrong. __init__ *can't* return anything, even if you want it to. So having return in an __init__ is just confusing to anyone reading the code, as it looks like you're trying to do something cryptic... when you're not, all you're doing is calling the function to setup some variables.


RE: colpage.py - Mekire - Apr-30-2017

Quote:It's just weird, not wrong.
Honestly, I would say despite not being wrong, it is still quite wrong.

__init__ can only return None and by convention we never do this explicitly for __init__.
The function is "wrong" because just like this thread shows, it confuses people.

Just write this and avoid confusion:
class colpage:
    def __init__(self, gutter=None, width=None, height=None):
        self.geometry( gutter=gutter, width=width, height=height)
Though I think changes should still be made to make attributes clearer and there is no need to use that function to both set and get these attributes.  As a C programmer I would think you would hate a function with an unpredictable return type.


RE: colpage.py - Skaperen - Apr-30-2017

my understanding has been that __init__ must return None.


RE: colpage.py - Mekire - Apr-30-2017

Implicitly. No one ever explicitly returns None for __init__ or indeed many functions at all. I would say only explicitly return None on a function where that has meaning. If your function changes a mutable it was passed or simply is setting attributes on a class it is customary to leave it off. Functions with no explicit return statement will return None regardless.

This goes for almost every return in the code you posted, but as stated specifically jumps out for the init.


RE: colpage.py - Skaperen - Apr-30-2017

(Apr-30-2017, 04:42 AM)Mekire Wrote: Though I think changes should still be made to make attributes clearer and there is no need to use that function to both set and get these attributes.  As a C programmer I would think you would hate a function with an unpredictable return type.
even in C i have made functions that set things and return the previous value for years.  and then in cases where there is nothing being set, it just gets the current setting.  so it's a get and set in one.  i have been doing duck typing, even in C. i called it context typing.

i've seen the get/set combining in other source code, too.  i've even seen cases where there being no way to not set a value, the caller set some value just to get the current value, then called again with what was returned to set it back.  i think it was from my IBM mainframe assembler days.