Skip to content

Commit

Permalink
Stricter document representation, and crazy meta-magical Python reader
Browse files Browse the repository at this point in the history
  • Loading branch information
brendonh committed Jul 8, 2009
1 parent a1ab7a0 commit 4070b15
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 35 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
*~
*.pyc
83 changes: 48 additions & 35 deletions pyth/document.py
Expand Up @@ -2,34 +2,35 @@
Abstract document representation
"""

class _PythBase(object):

class Document(object):
"""
Top-level item. One document is exactly one file.
Documents consist of a list of paragraphs.
"""

def __init__(self):
self.paragraphs = []
def __init__(self, properties={}, content=[]):
self.properties = {}
self.content = []

for (k,v) in properties.iteritems():
self[k] = v

for item in content:
self.append(item)


class Paragraph(object):
"""
Paragraphs contain zero or more text runs.
def __setitem__(self, key, value):
if key not in self.validProperties:
raise ValueError("Invalid %s property: %s" % (self.__class__.__name__, repr(key)))

They cannot contain other paragraphs (but see List).
self.properties[key] = value

They have no text markup properties, but may
have rendering properties (e.g. margins)
"""

def __init__(self):
self.runs = []

def append(self, item):
if not isinstance(item, self.contentType):
raise TypeError("Wrong content type: %s" % repr(type(item)))

self.content.append(item)


class Text(object):

class Text(_PythBase):
"""
Text runs are strings of text with markup properties,
like 'bold' or 'italic' (or 'hyperlink to ...').
Expand All @@ -38,27 +39,24 @@ class Text(object):
They do not inherit their properties from anything.
"""

def __init__(self, text, properties):
"""
text: A unicode string

properties: A dictionary of string names to arbitrary values.
e.g. {'bold': True}
"""
self.text = text
self.properties = properties
validProperties = ('bold', 'italic', 'underline', 'url')
contentType = unicode



class Hyperlink(Text):
class Paragraph(_PythBase):
"""
A text run which links to a URL.
Paragraphs contain zero or more text runs.
They cannot contain other paragraphs (but see List).
They have no text markup properties, but may
have rendering properties (e.g. margins)
"""

def __init__(self, text, url, properties):
Text.__init__(self, text, properties)
self.url = url
validProperties = ()
contentType = Text



Expand All @@ -68,6 +66,21 @@ class List(Paragraph):
A List is a Paragraph, so Lists can be nested.
"""

validProperties = ()
contentType = Paragraph



class Document(_PythBase):
"""
Top-level item. One document is exactly one file.
Documents consist of a list of paragraphs.
"""

def __init__(self):
self.paragraphs = []
validProperties = ()
contentType = Paragraph




44 changes: 44 additions & 0 deletions pyth/format.py
@@ -0,0 +1,44 @@
"""
Stuff for format implementations to subclass / use.
"""


class PythReader(object):
"""
Base class for all Pyth readers.
Readers must implement these methods.
"""

@classmethod
def read(self, source):
"""
source: An object to read the document from.
Usually (but not necessarily) a file object.
Returns: A pyth.document.Document object.
"""
pass



class PythWriter(object):
"""
Base class for all Pyth writers.
Writers must implement these methods.
"""

@classmethod
def write(self, document, target=None):
"""
document: An instance of pyth.document.Document
target: An object to write the document to.
Usually (but not necessarily) a file object.
If target is None, return something sensible
(like a StringIO object)
Returns: The target object
"""
pass
3 changes: 3 additions & 0 deletions pyth/plugins/__init__.py
@@ -0,0 +1,3 @@
"""
Document format plugins
"""
4 changes: 4 additions & 0 deletions pyth/plugins/python/__init__.py
@@ -0,0 +1,4 @@
"""
Python object input
"""

116 changes: 116 additions & 0 deletions pyth/plugins/python/reader.py
@@ -0,0 +1,116 @@
"""
Write Pyth documents straight in Python, a la Nevow's Stan.
"""

from pyth.format import PythReader
from pyth.document import *


class PythonReader(PythReader):

@classmethod
def read(self, source):
"""
source: A list of P objects.
"""
return Document(content=[c.toPyth() for c in source])



class _Shortcut(object):
def __init__(self, key):
self.key = key

def asDict(self):
return dict(((self.key, True),))


BOLD = _Shortcut("bold")
ITALIC = _Shortcut("italic")
UNDERLINE = _Shortcut("underline")


def _MetaPythonBase():
"""
Return a metaclass which implements __getitem__,
allowing e.g. P[...] instead of P()[...]
"""

class MagicGetItem(type):
def __new__(mcs, name, bases, dict):
klass = type.__new__(mcs, name, bases, dict)
mcs.__getitem__ = lambda _, k: klass()[k]
return klass

return MagicGetItem



class _PythonBase(object):
"""
Base class for Python markup objects, providing
stan-ish interface
"""

def __init__(self, *shortcuts, **properties):
self.properties = properties.copy()

for shortcut in shortcuts:
self.properties.update(shortcut.asDict())

self.content = []


def toPyth(self):
return self.pythType(self.properties,
[c.toPyth() for c in self.content])


def __getitem__(self, item):

if isinstance(item, tuple):
for i in item: self [i]
elif isinstance(item, int):
return self.content[item]
else:
self.content.append(item)

return self


def __str__(self):
return "%s(%s) [ %s ]" % (
self.__class__.__name__,
", ".join("%s=%s" % (k, repr(v)) for (k,v) in self.properties.iteritems()),
", ".join(repr(x) for x in self.content))



class P(_PythonBase):
__metaclass__ = _MetaPythonBase()
pythType = Paragraph


class L(_PythonBase):
__metaclass__ = _MetaPythonBase()
pythType = List


class T(_PythonBase):
__metaclass__ = _MetaPythonBase()
__repr__ = _PythonBase.__str__

def toPyth(self):
return Text(self.properties, self.content)



if __name__ == "__main__":
p = P [
T(BOLD),
T(ITALIC, url=u'http://www.google.com') [ u"Hello World" ],
T [ u"Hee hee hee" ] [ u"This seems to work" ]
]

doc = PythonReader.read((p,))

0 comments on commit 4070b15

Please sign in to comment.