diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e645833 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +*.pyc \ No newline at end of file diff --git a/pyth/document.py b/pyth/document.py index 6b8f619..2993c07 100644 --- a/pyth/document.py +++ b/pyth/document.py @@ -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 ...'). @@ -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 @@ -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 + + + + diff --git a/pyth/format.py b/pyth/format.py new file mode 100644 index 0000000..cbea279 --- /dev/null +++ b/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 diff --git a/pyth/plugins/__init__.py b/pyth/plugins/__init__.py new file mode 100644 index 0000000..f901dfe --- /dev/null +++ b/pyth/plugins/__init__.py @@ -0,0 +1,3 @@ +""" +Document format plugins +""" diff --git a/pyth/plugins/python/__init__.py b/pyth/plugins/python/__init__.py new file mode 100644 index 0000000..59b696c --- /dev/null +++ b/pyth/plugins/python/__init__.py @@ -0,0 +1,4 @@ +""" +Python object input +""" + diff --git a/pyth/plugins/python/reader.py b/pyth/plugins/python/reader.py new file mode 100644 index 0000000..848bfc1 --- /dev/null +++ b/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,)) +