import types from time import time as now from StringIO import StringIO from twisted.python import components from twisted.python.reflect import qual from twisted.web import resource, server class Proto(str): """Proto is a string subclass. Instances of Proto, which are constructed with a string, will construct Tag instances in response to __call__ and __getitem__, delegating responsibility to the tag. """ def __call__(self, **kw): return Tag(self)(**kw) def __getitem__(self, children): return Tag(self)[children] class raw(str): """Raw xml marker """ class directive(str): """Marker for a directive in a template """ class Tag(object): specials = ['data', 'renderer', 'observer', 'pattern', 'slot'] def __init__(self, tag, attributes=None, children=None, specials=None): self.tag = tag if attributes is None: self.attributes = {} else: self.attributes = attributes if children is None: self.children = [] else: self.children = children if specials is None: self._specials = {} else: self._specials = specials def __call__(self, **kw): """Change attributes of this tag. Three magic attributes may have values other than string values, and will be remembered in the context stack for later retrieval: data renderer observer See WovenContext.with for details. """ if not kw: return self for name in self.specials: if kw.has_key(name): setattr(self, name, kw[name]) del kw[name] for k, v in kw.items(): if k[0] == '_': k = k[1:] assert isinstance(v, str), "Non-special attribute values must be strings; %s found." % v self.attributes[k] = v return self def __getitem__(self, children): if not isinstance(children, (list, tuple)): children = [children] self.children.extend(children) return self def clone(self, deep=True): if deep: newchildren = [ hasattr(ch, 'clone') and ch.clone() or ch for ch in self.children ] else: newchildren = [] return Tag(self.tag, attributes=self.attributes.copy(), children=newchildren, specials=self._specials.copy()) def __repr__(self): return "Tag(%r, attributes=%r)" % (self.tag, self.attributes) def makeAccessors(special): def getSpecial(self): return self._specials.get(special) def setSpecial(self, data): self._specials[special] = data return getSpecial, setSpecial for name in Tag.specials: setattr(Tag, name, property(*makeAccessors(name))) class Invisible(Tag): """A Tag class which has a serializer which renders children but nothing for the Invisible instance itself. """ pass class ISerializable(components.Interface): def serialize(self, context, stream): """Serialize the adaptee to the given stream, with the given context stack if necessary. """ class ProtoSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): stream.write('<') stream.write(self.original) stream.write(' />') components.registerAdapter(ProtoSerializer, Proto, ISerializable) class TagSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): stream.write('<') stream.write(self.original.tag) if self.original.attributes: stream.write(' ') stream.write( ' '.join( ['%s="%s"' % (k, v) for (k, v) in self.original.attributes.items()] ) ) handleChildren = None if not self.original.renderer and not self.original.children and not self.original.data: stream.write(' />') else: context = context.with(self.original, data=self.original.data, renderer=self.original.renderer, observer=self.original.observer) stream.write('>') if self.original.renderer: ISerializable(self.original.renderer).serialize(context, stream) elif self.original.children: for child in self.original.children: ISerializable(child).serialize(context, stream) elif self.original.data: ISerializable(self.original.data).serialize(context, stream) stream.write('') components.registerAdapter(TagSerializer, Tag, ISerializable) class StringSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): ## quote it stream.write(self.original) components.registerAdapter(StringSerializer, str, ISerializable) components.registerAdapter(StringSerializer, unicode, ISerializable) class StringCastSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): ## quote it stream.write(str(self.original)) components.registerAdapter(StringCastSerializer, int, ISerializable) components.registerAdapter(StringCastSerializer, float, ISerializable) components.registerAdapter(StringCastSerializer, long, ISerializable) components.registerAdapter(StringCastSerializer, bool, ISerializable) class ListSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): for item in self.original: ISerializable(item).serialize(context, stream) components.registerAdapter(ListSerializer, list, ISerializable) components.registerAdapter(ListSerializer, tuple, ISerializable) class InvisibleSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): if self.original.renderer is not None: ISerializable(self.original.renderer).serialize(context, stream) components.registerAdapter(InvisibleSerializer, Invisible, ISerializable) class XmlSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): stream.write(self.original) components.registerAdapter(XmlSerializer, raw, ISerializable) class FunctionSerializer(components.Adapter): __implements__ = ISerializable, def serialize(self, context, stream): data = convertToData(context, context.locate(IData)) code = getattr(self.original, 'func_code', None) nocontext = code is None or code.co_argcount == 1 if nocontext: result = self.original(data) else: result = self.original(context, data) ISerializable(result).serialize(context, stream) components.registerAdapter(FunctionSerializer, types.FunctionType, ISerializable) components.registerAdapter(FunctionSerializer, type(str), ISerializable) def serialize(obj): sio = StringIO() ISerializable(obj).serialize(WovenContext(), sio) return sio.getvalue() class IData(components.Interface): """Any python object to be used as model data to be passed to view functions. Used for marking the context stack only. ANY python object is said to implement IData. """ class IGettable(components.Interface): def get(self, context): """Return the data Return any object """ class ISettable(components.Interface): def set(self, context, data): """Set the data This might be removed. """ class IContainer(components.Interface): def child(self, context, name): """Return a conceptual child; an attribute, or a key, or the result of a function call. Returns any object; may be adapted to IAccessor if necessary Return None if the adaptee does not have a child with the given name. """ def convertToData(context, obj): adapter = IGettable(obj, None, persist=False) if adapter is not None: obj = adapter.get(context) return obj class DirectiveAccessor(components.Adapter): __implements__ = IGettable, def get(self, context): data = context.locate(IData, depth=2) container = IContainer( data, None, persist=False ) if container is None: raise RuntimeError, "%s does not implement IContainer, and there is no registered adapter." % data child = container.child(context, self.original) return convertToData(context, child) components.registerAdapter(DirectiveAccessor, directive, IGettable) class FunctionAccessor(components.Adapter): __implements__ = IGettable, def get(self, context): # self.original is the data at depth=1 return self.original(context, context.locate(IData, depth=2)) components.registerAdapter(FunctionAccessor, types.FunctionType, IGettable) class DictionaryContainer(components.Adapter): __implements__ = IContainer, IGettable def child(self, context, name): return self.original.get(name, None) def get(self, context): return dict( [(k, convertToData(context, v)) for (k, v) in self.original.items()] ) components.registerAdapter(DictionaryContainer, types.DictType, IContainer) components.registerAdapter(DictionaryContainer, types.DictType, IGettable) class ListContainer(components.Adapter): __implements__ = IContainer, IGettable def child(self, context, name): try: i = int(name) except ValueError: return None return self.original[i] def get(self, context): return [convertToData(context, x) for x in self.original] components.registerAdapter(ListContainer, types.ListType, IContainer) components.registerAdapter(ListContainer, types.TupleType, IGettable) def specialMatches(tag, special, pattern): """Generate special attribute matches starting with the given tag; if a match is found, do not look any deeper below that match. """ if getattr(tag, special, None) == pattern: yield tag else: for child in getattr(tag, 'children', []): for match in specialMatches(child, special, pattern): yield match class WovenContext(object): cloned = False def __init__(self, parent=None, tag=None): self.tag = tag self.parent = parent self._rememberances = {} def __repr__(self): if self.parent: parent = "parent=%s" % self.parent else: parent = '' if self._rememberances: remember = "\n%s\n" % self._rememberances else: remember = '' return """Context(%s%s)""" % (remember, parent) def remember(self, adapter, interface=None): """Remember an object that implements some interfaces. Later, calls to locate which are passed an interface implemented by this object will return this object. If the 'interface' argument is supplied, this object will only be remembered for this interface, and not any of the other interfaces it implements. """ if interface is None: interfaceList = components.getInterfaces(adapter) if not interfaceList: interfaceList = [IData] else: interfaceList = [interface] for interface in interfaceList: self._rememberances[qual(interface)] = adapter def with(self, tag, data=None, renderer=None, observer=None): """Remember a few things in a new context stack entry. tag: the tag the new context stack entry will be associated with. data: The data to pass to any rendering functions. renderer: The rendering function to use to render this node. observer: The observer function to notify when an event occurs to this node. """ new = WovenContext(self, tag) if data is not None: new.remember(data, IData) if renderer is not None: # push a renderer onto the stack pass if observer is not None: # push an observer onto the stack pass return new def locate(self, interface, depth=1): """Locate an object which implements a given interface. Objects will be searched through the context stack top down. """ key = qual(interface) if self.parent is None: return None if self._rememberances.has_key(key): depth -= 1 if not depth: return self._rememberances[key] return self.parent.locate(interface, depth) def generatePatterns(self, pattern): """Generate clones of pattern tags forever, looping around to the beginning when we run out of unique matches. """ while 1: for x in specialMatches(self.tag, 'pattern', pattern): yield x.clone() def locateSlots(self, slot): """Locate and return an existing slot in the current context tag, clearing children in the process. This will cause the existing tag to be cloned to avoid disturbing the template; you could return context.tag or another tag after calling this. """ return specialMatches(self.tag, 'slot', slot) class IRenderable(components.Interface): """Something that can be rendered """ class Renderer(resource.Resource): __implements__ = IRenderable, def __init__(self, doc): self.doc = doc resource.Resource.__init__(self) def getChild(self, name, request): if name == '': return self return resource.Resource.getChild(self, name, request) def render(self, request): context = WovenContext() context.remember(self, IRenderable) start = now() ISerializable(self.doc).serialize(context, request) print "TIME", now() - start request.finish() return server.NOT_DONE_YET