[Doc-SIG] PythonPoint front-end

Aahz aahz@pythoncraft.com
Fri, 25 Apr 2003 11:24:16 -0400


--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Fri, Apr 25, 2003, Patrick K. O'Brien wrote:
> Aahz <aahz@pythoncraft.com> writes:
>> 
>> If you'd like, you can have my front-end to PythonPoint.  (I created a
>> dead-simple half-reSTed syntax to avoid the drudgery of writing XML.)
>> Once Kevin does his work, it should be simple to modify my front-end to
>> write out reST.
> 
> Sure, I'd like to take a look at that.  Thanks.  I also see that
> Richard Jones has a pythonpoint writer in his docutils sandbox.  Has
> anyone used it?

Here you go.  I'm including my PyCon objects presentation so you can see
how it works.  (Haven't tried using Richard's bit; I wrote mine a year
ago when reST was really in the Dark Ages. ;-)

(It'll be available on my web site Real Soon Now....)
-- 
Aahz (aahz@pythoncraft.com)           <*>         http://www.pythoncraft.com/

"In many ways, it's a dull language, borrowing solid old concepts from
many other languages & styles:  boring syntax, unsurprising semantics,
few automatic coercions, etc etc.  But that's one of the things I like
about it."  --Tim Peters on Python, 16 Sep 93

--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="stx2xml.py"

import sys
import os
import re
import imp
import cgi

#cfg = imp.new_module('cfg')
class cfg: pass

tagDict = {}

class Tag:
    closes = []
    def __init__(self):
        self._attr = {}
        self.elements = []
    def default(self, value):
        pass
    def end(self):
        raise NotImplementedError
    def output(self):
        raise NotImplementedError
    def get_attrs(self):
        tmp = []
        for key,value in self._attr.iteritems():
            tmp.append('%s="%s"' % (key,value))
        return ' '.join(tmp)



class Presentation(Tag):
    def __init__(self):
        Tag.__init__(self)
        tagStack = []

    def pragma_outputFile(self, value):
        self.outputFile = value

    def pragma_style(self, value):
        mod, func = value.split(',')
        self.styleMod, self.styleFunc = mod, func
        f, p, d = imp.find_module(mod)
        mod = imp.load_module(mod, f, p, d)
        func = getattr(mod, func)
        cfg.styles = func()

    def pragma_frame(self, value):
        cfg.pageFrame = tuple(value.split(','))

    def pragma_footer(self, value):
        cfg.footerFrame = tuple(value.split(','))

    def pragma_codeRatio(self, value):
        cfg.codeRatio = float(value)

    def output(self):
        self.setFooters()
        tmp = []
        tmp.append('<presentation %s>' % self.get_attrs())
        mod, func = self.styleMod, self.styleFunc
        tmp.append('<stylesheet module="%s" function="%s"/>' % (mod, func))
        tmp.append('<section name="Main">')
        for slide in self.elements:
            tmp.append(slide.output())
        tmp.append('</section>\n</presentation>')
        return '\n\n'.join(tmp)

    def setFooters(self):
        numPages = len(self.elements)
        pageNo = 0
        for slide in self.elements:
            pageNo += 1
            slide.pageNo = pageNo
            slide.numPages = numPages

tagDict['presentation'] = Presentation



class Para(Tag):
    tagName = 'para'
    styleName = None
    reCode = re.compile("``([^`]+)``")

    def __init__(self):
        Tag.__init__(self)
        self.codeSize = None

    def getCodeSize(self):
        fontSize = cfg.styles[self.styleName].fontSize
        self.codeSize = round(fontSize * cfg.codeRatio, 1)

    def parse(self, line):
        if self.codeSize is None:
            self.getCodeSize()
        repl = r'<font name="Courier" size="%s">\1</font>' % self.codeSize
        return self.reCode.sub(repl, line)

    def default(self, value):
        value = self.parse(value)
        self.elements.append(value)

    def end(self):
        pass

    def output(self):
        outdict = {
            'tagName': self.tagName,
            'style': 'style="%s"' % self.styleName,
            'data': '\n'.join(self.elements)
            }
        tmp = '<%(tagName)s %(style)s>%(data)s</%(tagName)s>' % outdict
        return tmp

Para.closes = [Para]



class PreFmt(Para):
    tagName = 'prefmt'
    styleName = 'Normal'
tagDict['prefmt'] = PreFmt



class Slide(Tag):
    def __init__(self):
        Tag.__init__(self)

    def pragma_title(self, value):
        self._attr['title'] = value
        tmp = Title()
        tmp.default(value)
        self.elements.append(tmp)

    def pragma_head(self, value):
        self._attr['title'] = value.replace("``", "")
        tmp = Head()
        tmp.default(value)
        self.elements.append(tmp)

    def end(self):
        pass

    def output(self):
        tmp = []
        tmp.append('<slide %s>' % self.get_attrs())
        tmp.append(make_frame(cfg.pageFrame))
        for tag in self.elements:
            tmp.append(tag.output())
        tmp.append('</frame>')
        tmp.append(make_frame(cfg.footerFrame))
        pageNo, numPages = self.pageNo, self.numPages
        f = '<para style="Footer">%s</para>' % pageNo
        tmp.append(f)
        tmp.append('</frame>')
        tmp.append('</slide>')
        return '\n'.join(tmp)

Slide.closes = [Para, Slide]
tagDict['slide'] = Slide



class Title(Para):
    styleName = 'title'
tmp = Title
tagDict[tmp.styleName] = tmp



class Head(Para):
    styleName = 'head'
tmp = Head
tagDict[tmp.styleName] = tmp



class SmallTitle(Para):
    styleName = 'smalltitle'
tmp = SmallTitle
tagDict[tmp.styleName] = tmp



class Normal(Para):
    styleName = 'normal'
tmp = Normal
tagDict[tmp.styleName] = tmp



class Bullet(Para):
    styleName = 'Bullet'                # PythonPoint needs cap :-(
tagDict['bullet'] = Bullet



class Indent(Para):
    styleName = 'indent'
tmp = Indent
tagDict[tmp.styleName] = tmp



class URL(Para):
    styleName = 'url'
tmp = URL
tagDict[tmp.styleName] = tmp



class Code(PreFmt):
    styleName = 'code'
    def default(self, value):
    	value = cgi.escape(value)
	PreFmt.default(self, value)
    def pragma_include(self, value):
        data = open(value).read()
        data = cgi.escape(data)
        self.elements.append(data)
tmp = Code
tagDict[tmp.styleName] = tmp



class Spacer(Para):
    def output(self):
        return '<spacer %s/>' % self.get_attrs()
tagDict['spacer'] = Spacer



class NumberedList(Para):
    def default(self, value):
        self.elements.append(value)
    def output(self):
        i = 1
        tmp = []
        for item in self.elements:
            s = '<para style="NumList">%s. %s</para>' % (i, item)
            tmp.append(s)
	    i += 1
        return '\n'.join(tmp)
tagDict['numlist'] = NumberedList



def make_frame(frame):
    return '<frame x="%s" y="%s" width="%s" height="%s">' % frame



def dispatch_tag(tag, root):
    newTag = tagDict[tag]()
    tagStack = root.tagStack
    #print tag, newTag.closes
    for tagType in newTag.closes:
        currTag = tagStack[-1]
        #print isinstance(currTag, tagType)
        if isinstance(currTag, tagType):
            currTag.end()
            tagStack.pop()
    tagStack[-1].elements.append(newTag)
    tagStack.append(newTag)
    #print tagStack



def dispatch_attrib(line, root):
    currTag = root.tagStack[-1]
    key, value = line.split(' ', 1)
    currTag._attr[key] = value



def dispatch_pragma(line, root):
    currTag = root.tagStack[-1]
    pragma, value = line.split(' ', 1)
    meth = getattr(currTag, 'pragma_' + pragma)
    meth(value)

def dispatch_comment(line, root):
    pass



dispatch = {
    '.': dispatch_tag,
    '=': dispatch_attrib,
    '@': dispatch_pragma,
    '#': dispatch_comment,
    }

def parse(input):
    root = Presentation()
    root.tagStack = [root]
    try:
        for line in input:
            line = line.rstrip()
            if not line:
                root.tagStack[-1].default(line)
                continue
            ch = line[0]
            if ch in dispatch:
                # handle continuation lines in interactive mode
                if line[1] == '.':
                    root.tagStack[-1].default(line)
                else:
                    dispatch[ch](line[1:], root)
            else:
                root.tagStack[-1].default(line)
    except KeyError:
        print line
        raise
    return root



def write(root):
    f = open(root.outputFile, 'w')
    f.write(root.output())
    # print root.output()



def process(input):
    input = open(input)
    root = parse(input)
    write(root)



if __name__ == '__main__':
    input = sys.argv[1]
    process(input)

--0OAP2g/MAC+5xKAE
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="pyobj.stx"

=filename pyobj.pdf
@outputFile pyobj.xml
@style style,Styles
@frame 0,0,782,612
@footer 0,5,787,25
@codeRatio 0.85

.slide
@title Python Objects and New-style Classes
.spacer 
=height 40
.smalltitle
<font size="60">Aahz</font>
.spacer 
=height 20
.smalltitle
``aahz@pythoncraft.com``
.smalltitle
``http://pythoncraft.com/``
.spacer 
=height 60
.smalltitle
Powered by PythonPoint
.spacer 
=height 10
.smalltitle
``http://www.reportlab.com/``

.slide
=outlinelevel 1
@head Before we begin...
.bullet
I'm hearing-impaired
.indent
Please speak slowly and clearly

.slide
@head Overall
.bullet
Goal: understand objects
.indent
Use new-style classes to illuminate
.url
http://www.python.org/2.2.2/descrintro.html
.indent
No classic classes
.bullet
Timing
.indent
30 minutes core material
.indent
15 minutes optional material
.indent
# Deal with stupid ReportLab entity bug
15-30 minutes Q&amp;amp;A

.slide
@head Contents
.bullet
Object Basics
.bullet
Type/class tangle
.bullet
Memory management
.bullet
Optional Material
.indent
Mutable/immutable

.slide
@head Object Basics
.bullet
Objects
.bullet
Targets
.bullet
Namespace/Scope

.slide
=outlinelevel 1
@head Objects Defined
.bullet
Object is structured memory
.bullet
Object has:
.indent
Type
.indent
Namespace (attributes)
.indent
Value (optional: int/string objects have value, files don't)

.slide
=outlinelevel 1
@head Everything an Object 
.bullet
All data is object
.indent
Functions, types, classes, class instances, iterators, generators,
modules, and files all first-class objects
.bullet
Capabilities
.indent
Objects all have same potential capabilities, but objects
choose which capabilities to expose.
.indent
The type determines the capabilities.
.indent
("Capability" in its generic sense, essentially 
"set of data/methods" -- not the python-dev thread. ;-)

.slide
=outlinelevel 1
@head Accessing objects
.bullet
Targets contain bindings to objects
.indent
"Binding" used instead of "reference" because you can only deal with
objects directly, never the references themselves (except in C API).

.slide
=outlinelevel 1
@head Kinds of Targets
.bullet
Names
.bullet
Attributes
.indent
Names in object space, accessed with dot notation
.bullet
Computed
.indent
Indexes / Keys
.bullet
Anonymous
.indent
E.g. ``return`` or function defaults
.indent
Important for understanding refcounts

.slide
=outlinelevel 1
@head Names
.bullet
What's a name?
.indent
Bare word at builtin, module global, or function scope (more on scope
shortly)
.bullet
"Name" instead of "variable"
.indent
Names in Python don't contain data, only bindings; they're used like
variables, mostly, but use "names" instead to remember the semantics

.slide
=outlinelevel 1
@head Each Object a Namespace
.bullet
Attributes
.indent
Objects contain dict that maps key/value pairs to names and objects.
For some objects (primarily built-in types, function locals, and some
new-style classes), dict gets mapped to vector for reduced memory
consumption and speed.

.slide
=outlinelevel 1
@head Binding Operations
.bullet
Creating names
.indent
``=``, ``for`` (and list comprehensions), ``def``, 
``import`` (all forms), ``class``, function/method parameters,
``except`` block (second parameter)
.bullet
Other targets
.indent
``=`` (sequences/maps), ``return``, ``yield``, ``print``, 
function/method default parameters

.slide
=outlinelevel 1
@head Namespace Demo
.bullet
``namespace.py``

.slide
=outlinelevel 1
@head Scope
.bullet
Two scope hierarchies:
.indent
Execution scope
.indent
Class inheritance scope
.bullet
Read-only
.indent
Non-local scopes are read-only unless made explicit

.slide
=outlinelevel 1
@head Execution Scope
.bullet
Searches three namespaces in order:
.indent
Function local
.indent
Module global
.indent
Builtins
.bullet
Shadowing
.indent
Locals shadow globals shadow builtins

.slide
=outlinelevel 1
@head Nested Scopes
.bullet
Functions inside functions
.bullet
Lexical scope
.indent
Not dynamic scope
.indent
Allows static analysis of source code
.bullet
Python 2.1
.indent
``from __future__ import nested_scopes``

.slide
=outlinelevel 1
@head Class Inheritance Scope
.bullet
Non-standard "scope" usage
.indent
Some people disagree with me that class attribute/method inheritance
should be discussed as a "scope", but it's a convenient way to explain
the similarities with execution scope
.bullet
``self``
.indent
Used in methods to distinguish between execution scope (implicit as
in other functions) and the class inheritance scope (which is made
explicit through ``self``)

.slide
=outlinelevel 1
@head Scope Demo
.bullet
``scope.py``

.slide
@head Type/Class Tangle
.bullet
Inheritance vs. creation
.bullet
Class vs. instance

.slide
=outlinelevel 1
@head Inheritance vs. creation
.bullet 
Inheritance
.indent
Attribute search path
.bullet
Type
.indent
Types are objects that create other objects
.indent
Metaclass is type that generates classes; class is type that
generates class instances
.indent
``type(Foo) == Foo.__class__``
.bullet
Instance
.indent
Instance is an object created by a type

.slide
=outlinelevel 1
@head Class vs. Instance
.bullet
Instance attribute
.indent
Access with ``self``
.bullet
Class attribute
.indent
Access with class name
.indent
But remember that a class is also an instance...

.slide
=outlinelevel 1
@head More Inheritance
.bullet
Bases vs. Types
.indent
An instance's inheritance searches its type, then its type's base
classes.  Inheritance does NOT search the type's type.

.slide
=outlinelevel 1
@head Type/Class Demo
.bullet
``typeclass.py``

.slide
@head Memory Management
.bullet
Reference counts
.bullet
Garbage collection
.bullet
``__slots__``

.slide
=outlinelevel 1
@head Reference Counts
.bullet
Object deletion
.indent
Objects are deleted when no bindings reference object
.bullet
Binding
.indent
Each binding increases refcount
.bullet
Decrease refcount
.indent
Rebinding
.indent
Name going out of scope
.indent
``del``

.slide
=outlinelevel 1
@head ``del``
.bullet
Deletes bindings, not objects
.bullet
Can ``del`` parts of objects:
.spacer
=height 10
.code
>>> L = [1,2,3,4,5]
>>> del L[1:3]
>>> L
[1, 4, 5]
>>> d = {'a': 1, 'b': 2, 'c': 5}
>>> del d['b']
>>> d
{'a': 1, 'c': 5}

.slide
=outlinelevel 1
@head Refcount Cascades
.bullet
Objects refer to other objects
.indent
When an object gets deleted, all objects it refers to get decremented and
may be deleted -- leading to a cascade of object deletions
.bullet
Large lists/dicts
.indent
Deleting a large list or dict can take a long time as each object in the
list/dict needs to be walked

.slide
=outlinelevel 1
@head GC
.bullet
Garbage collection
.indent
In addition to refcount, not instead of
.bullet
Cycles
.indent
Occur when two or more objects refer to each other; refcount
can't go to zero.  If no external targets refer to cycle,
cycle is "garbage" and GC deletes it.
.bullet
``__del__()``
.indent
Objects with ``__del__()`` can't be GC'd because of ordering problem

.slide
=outlinelevel 1
@head Refcount/GC Demo
.bullet
``refcount_gc.py``

.slide
=outlinelevel 1
@head ``__slots__``
.bullet
Attributes
.indent
Normally stored in dict
.indent
``__slots__`` creates indexed vector
.indent
Attribute names stored with class, not instance

.slide
@head Mutable vs. Immutable
.bullet
Immutable objects
.indent
Strings, numbers, tuples, some class instances, and combinations
.bullet
Mutable objects
.indent
Lists, dicts, most classes and class instances
.bullet
Why immutable?
.indent
Dict keys, space efficiency

.slide
=outlinelevel 1
@head Mutable Subtleties
.bullet
Lists embedded in tuples
.spacer
=height 10
.code
>>> a = (1, ['foo'], 's')
>>> a[1] = {'foo': 'bar'}
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
>>> a[1].append('bar')
>>> a
(1, ['foo', 'bar'], 's')
.spacer
=height 5
.bullet
Classes
.indent
Cannot force class immutable; it's an implied contract in code and
documentation (must use C API to force immutable)

.slide
=outlinelevel 1
@head Copy Semantics (for function calls)
.bullet
Immutables don't need copy
.bullet
Mutables must be copied
.indent
If you don't want them modified, that is

.slide
=outlinelevel 1
@head Copy Example
.spacer
=height 15
.normal
``list.sort()`` is an in-place operation; here we create a function that
returns the list
.spacer
=height 10
.code
>>> def sort(L):
...     L.sort()
...     return L
...
>>> L = [11, 7, 12, 5]
>>> sort(L[:])
[5, 7, 11, 12]
>>> L
[11, 7, 12, 5]
>>> sort(L)
[5, 7, 11, 12]
>>> L
[5, 7, 11, 12]
.spacer
=height 15
.normal
Notice how failing to make copy modifies original list

.slide
=outlinelevel 1
@head Mutability Again
.bullet
``=`` vs. ``+=`` (and family)
.indent
``=`` always [re]binds
.indent
mutable vs. immutable
.indent
``+=`` may rebind ``self`` to original object if mutable (but not
required)

.slide
=outlinelevel 1
@head Tuples and ``+=``
.spacer
=height 10
.code
>>> a = (1, ['foo'], 'xyzzy')
>>> a[1].append('bar')
>>> a
(1, ['foo', 'bar'], 'xyzzy')
>>> a[1] = 9
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
>>> a
(1, ['foo', 'bar'], 'xyzzy')
>>> a[1] += ['spam']
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
>>> a
(1, ['foo', 'bar', 'spam'], 'xyzzy')

.slide
=outlinelevel 1
@head ``+=`` expansion
.spacer
=height 10
.code
a.__setitem__(1, 
    a.__getitem__(1).__iadd__(['spam']))
.spacer
=height 15
.normal
Rewrite with temp vars:
.spacer
=height 10
.code
>>> a = (1, ['foo', 'bar'], 'xyzzy')
>>> x = a.__getitem__(1)
>>> x
['foo', 'bar']
>>> x.__iadd__(['spam'])
['foo', 'bar', 'spam']
>>> a.__setitem__(1, x)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'tuple' object has 
    no attribute '__setitem__'
>>> a
(1, ['foo', 'bar', 'spam'], 'xyzzy')


--0OAP2g/MAC+5xKAE--