[Python-Dev] Adding library modules to the core

Eric S. Raymond esr@thyrsus.com
Mon, 7 Aug 2000 21:23:34 -0400


--EVF5PPMfhYS0aIcm
Content-Type: text/plain; charset=us-ascii

Guido van Rossum <guido@beopen.com>:
> [ESR]
> > 1. Has anybody got a vote on the menubrowser framwork facility I described?
> 
> Eric, as far as I can tell you haven't shown the code or given a
> pointer to it.  I explained to you that your description left me in
> the dark as to what it does.  Or did I miss a pointer?  It seems your
> module doesn't even have a name!  This is a bad way to start a
> discussion about the admission procedure.  Nothing has ever been
> accepted into Python before the code was written and shown.

Easily fixed.  Code's in an enclosure.
 
> > 1. Do we have a procedure for vetting modules for inclusion in the stock
> > distribution?  If not, should be institute one?
> 
> Typically, modules get accepted after extensive lobbying and agreement
> from multiple developers.  The definition of "developer" is vague, and
> I can't give a good rule -- not everybody who has been admitted to the
> python-dev list has enough standing to make his opinion count!

Understood, and I assume one of those insufficient-standing people is
*me*, given my short tenure on the list, and I cheerfully accept that.
The real problem I'm going after here is that this vague rule won't
scale well.

> Basically, I rely a lot on what various people say, but I have my own
> bias about who I trust in what area.  I don't think I'll have to
> publish a list of this bias, but one thing is clear: I;m not counting
> votes! 

I wasn't necessarily expecting you to.  I can't imagine writing a
procedure in which the BDFL doesn't retain a veto.

> I don't know mimectl or Vladimir's module (how does it compare to
> mmap?).

Different, as Thomas Wouters has already observed.  Vladimir's module is more
oriented towards supporting semaphores and exclusion.  At one point many months
ago, before Vladimir was on the list, I looked into it as a way to do exclusion
locking for shared shelves.  Vladimir and I even negotiated a license change
with INRIA so Python could use it.  That was my first pass at sharable 
shelves; it foundered on problems with the BSDDB 1.85 API.  But shm would
still be worth having in the core librariy, IMO.

The mimecntl module supports classes for representing MIME objects that
include MIME-structure-sensitive mutation operations.  Very strong candidate
for inclusion, IMO.

> > Now, assuming I do 3, would I need to go through the vote process
> > on each of these, or can I get a ukase from the BDFL authorizing me to
> > fold in stuff?
> 
> Sorry, I don't write blank checks.

And I wasn't expecting one.  I'll write up some thoughts about this in the PEP.
 
> > I realize I'm raising questions for which there are no easy answers.
> > But Python is growing.  The Python social machine needs to adapt to
> > make such decisions in a more timely and less ad-hoc fashion.  I'm
> > not attached to being the point person in this process, but
> > somebody's gotta be.
> 
> Watch out though: if we open the floodgates now we may seriously
> deteriorate the quality of the standard library, without doing much
> good.

The alternative is to end up with a Perl-like Case of the Missing Modules,
where lots of things Python writers should be able to count on as standard
builtins can't realistically be used, because the users they deliver to
aren't going to want to go through a download step.
 
> I'd much rather see an improved Vaults of Parnassus (where every
> module uses distutils and installation becomes trivial) than a
> fast-track process for including new code in the core.

The trouble is that I flat don't believe in this solution.  It works OK
for developers, who will be willing to do extra download steps -- but it
won't fly with end-user types.

> That said, I think writing a bunch of thoughts up as a PEP makes a lot
> of sense!

I've applied to initiate PEP 2.
-- 
		<a href="http://www.tuxedo.org/~esr">Eric S. Raymond</a>

Hoplophobia (n.): The irrational fear of weapons, correctly described by 
Freud as "a sign of emotional and sexual immaturity".  Hoplophobia, like
homophobia, is a displacement symptom; hoplophobes fear their own
"forbidden" feelings and urges to commit violence.  This would be
harmless, except that they project these feelings onto others.  The
sequelae of this neurosis include irrational and dangerous behaviors
such as passing "gun-control" laws and trashing the Constitution.

--EVF5PPMfhYS0aIcm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="menubrowser.py"

# menubrowser.py -- framework class for abstract browser objects

from sys import stderr

class MenuBrowser:
    "Support abstract browser operations on a stack of indexable objects."
    def __init__(self, debug=0, errout=stderr):
        self.page_stack = []
        self.selection_stack = []
        self.viewbase_stack = []
        self.viewport_height = 0
        self.debug = debug
        self.errout = errout

    def match(self, a, b):
        "Browseable-object comparison."
        return a == b

    def push(self, browseable, selected=None):
        "Push a browseable object onto the location stack."
        if self.debug:
            self.errout.write("menubrowser.push(): pushing %s=@%d, selection=%s\n" % (browseable, id(browseable), `selected`))
        selnum = 0
        if selected == None:
            if self.debug:
                self.errout.write("menubrowser.push(): selection defaulted\n")
        else:
            for i in range(len(browseable)):
                selnum = len(browseable) - i - 1
                if self.match(browseable[selnum], selected):
                     break
            if self.debug:
                self.errout.write("menubrowser.push(): selection set to %d\n" % (selnum))
        self.page_stack.append(browseable)
        self.selection_stack.append(selnum)
        self.viewbase_stack.append(selnum - selnum % self.viewport_height)
        if self.debug:
            object = self.page_stack[-1]
            selection = self.selection_stack[-1]
            viewbase = self.viewbase_stack[-1]
            self.errout.write("menubrowser.push(): pushed %s=@%d->%d, selection=%d, viewbase=%d\n" % (object, id(object), len(self.page_stack), selection, viewbase))

    def pop(self):
        "Pop a browseable object off the location stack."
        if not self.page_stack:
            if self.debug:
                self.errout.write("menubrowser.pop(): stack empty\n")
            return None
        else:
            item = self.page_stack[-1]
            self.page_stack = self.page_stack[:-1]
            self.selection_stack = self.selection_stack[:-1]
            self.viewbase_stack = self.viewbase_stack[:-1]
            if self.debug:
                if len(self.page_stack) == 0:
                    self.errout.write("menubrowser.pop(): stack is empty.")
                else:
                    self.errout.write("menubrowser.pop(): new level %d, object=@%d, selection=%d, viewbase=%d\n" % (len(self.page_stack), id(self.page_stack[-1]), self.selection_stack[-1], self.viewbase_stack[-1]))
            return item

    def stackdepth(self):
        "Return the current stack depth."
        return len(self.page_stack)

    def list(self):
        "Return all elements of the current object that ought to be visible."
        if not self.page_stack:
            return None
        object = self.page_stack[-1]
        selection = self.selection_stack[-1]
        viewbase = self.viewbase_stack[-1]

        if self.debug:
            self.errout.write("menubrowser.list(): stack level %d. object @%d, listing %s\n" % (len(self.page_stack)-1, id(object), object[viewbase:viewbase+self.viewport_height]))

        # This requires a slice method
        return object[viewbase:viewbase+self.viewport_height]

    def top(self):
        "Return the top-of-stack menu"
        if self.debug >= 2:
            self.errout.write("menubrowser.top(): level=%d, @%d\n" % (len(self.page_stack)-1,id(self.page_stack[-1])))
        return self.page_stack[-1]

    def selected(self):
        "Return the currently selected element in the top menu."
        object = self.page_stack[-1]
        selection = self.selection_stack[-1]
        if self.debug:
            self.errout.write("menubrowser.selected(): at %d, object=@%d, %s\n" % (len(self.page_stack)-1, id(object), self.selection_stack[-1]))
        return object[selection]

    def viewbase(self):
        "Return the viewport base of the current menu."
        object = self.page_stack[-1]
        selection = self.selection_stack[-1]
        base = self.viewbase_stack[-1]
        if self.debug:
            self.errout.write("menubrowser.viewbase(): at level=%d, object=@%d, %d\n" % (len(self.page_stack)-1, id(object), base,))
        return base

    def thumb(self):
        "Return top and bottom boundaries of a thumb scaled to the viewport."
        object = self.page_stack[-1]
        windowscale = float(self.viewport_height) / float(len(object))
        thumb_top = self.viewbase() * windowscale
        thumb_bottom = thumb_top + windowscale * self.viewport_height - 1
        return (thumb_top, thumb_bottom)

    def move(self, delta=1, wrap=0):
        "Move the selection on the current item downward."
        if delta == 0:
            return
        object = self.page_stack[-1]
        oldloc = self.selection_stack[-1]

        # Change the selection.  Requires a length method
        if oldloc + delta in range(len(object)):
            newloc = oldloc + delta
        elif wrap:
            newloc = (oldloc + delta) % len(object)
        elif delta > 0:
            newloc = len(object) - 1
        else:
            newloc = 0
        self.selection_stack[-1] = newloc

        # When the selection is moved out of the viewport, move the viewbase
        # just part enough to track it.
        oldbase = self.viewbase_stack[-1]
        if newloc in range(oldbase, oldbase + self.viewport_height):
            pass
        elif newloc < oldbase:
            self.viewbase_stack[-1] = newloc
        else:
            self.scroll(newloc - (oldbase + self.viewport_height) + 1)

        if self.debug:
            self.errout.write("menubrowser.down(): at level=%d, object=@%d, old selection=%d, new selection = %d, new base = %d\n" % (len(self.page_stack)-1, id(object), oldloc, newloc, self.viewbase_stack[-1]))

        return (oldloc != newloc)

    def scroll(self, delta=1, wrap=0):
        "Scroll the viewport up or down in the current option."
        print "delta:", delta
        object = self.page_stack[-1]
        if not wrap:
            oldbase = self.viewbase_stack[-1]
            if delta > 0 and oldbase+delta > len(object)-self.viewport_height:
                return
            elif delta < 0 and oldbase + delta < 0:
                return
        self.viewbase_stack[-1] = (self.viewbase_stack[-1] + delta) % len(object)

    def dump(self):
        "Dump the whole stack of objects."
        self.errout.write("Viewport height: %d\n" % (self.viewport_height,))
        for i in range(len(self.page_stack)):
            self.errout.write("Page: %d\n" % (i,))
            self.errout.write("Selection: %d\n" % (self.selection_stack[i],))
            self.errout.write(`self.page_stack[i]` + "\n");

    def next(self, wrap=0):
        return self.move(1, wrap)

    def previous(self, wrap=0):
        return self.move(-1, wrap)

    def page_down(self):
        return self.move(2*self.viewport_height-1)

    def page_up(self):
        return self.move(-(2*self.viewport_height-1))

if __name__ == '__main__': 
    import cmd, string, readline

    def itemgen(prefix, count):
        return map(lambda x, pre=prefix: pre + `x`, range(count))

    testobject = menubrowser()
    testobject.viewport_height = 6
    testobject.push(itemgen("A", 11))

    class browser(cmd.Cmd):
        def __init__(self):
            self.wrap = 0
            self.prompt = "browser> "

        def preloop(self):
            print "%d: %s (%d) in %s" %  (testobject.stackdepth(), testobject.selected(), testobject.viewbase(), testobject.list())

        def postloop(self):
            print "Goodbye."

        def postcmd(self, stop, line):
            self.preloop()
            return stop

        def do_quit(self, line):
            return 1

        def do_exit(self, line):
            return 1

        def do_EOF(self, line):
            return 1

        def do_list(self, line):
            testobject.dump()

        def do_n(self, line):
            testobject.next()

        def do_p(self, line):
            testobject.previous()

        def do_pu(self, line):
            testobject.page_up()

        def do_pd(self, line):
            testobject.page_down()

        def do_up(self, line):
            if string.strip(line):
                n = string.atoi(line)
            else:
                n = 1
            testobject.move(-n, self.wrap)

        def do_down(self, line):
            if string.strip(line):
                n = string.atoi(line)
            else:
                n = 1
            testobject.move(n, self.wrap)

        def do_s(self, line):
            if string.strip(line):
                n = string.atoi(line)
            else:
                n = 1
            testobject.scroll(n, self.wrap)

        def do_pop(self, line):
            testobject.pop()

        def do_gen(self, line):
            tokens = string.split(line)
            testobject.push(itemgen(tokens[0], string.atoi(tokens[1])))

        def do_dump(self, line):
            testobject.dump()

        def do_wrap(self, line):
            self.wrap = 1 - self.wrap
            if self.wrap:
                print "Wrap is now on."
            else:
                print "Wrap is now off."

        def emptyline(self):
            pass

    browser().cmdloop()

--EVF5PPMfhYS0aIcm--