################################################## # SPYCE - Python-based HTML Scripting # Copyright (c) 2002 Rimon Barr. # # Refer to spyce.py # CVS: $Id: spyceTag.py 1174 2006-08-29 17:22:17Z ellisj $ ################################################## __doc__ = '''Spyce tags functionality.''' import sys, inspect import spyce, spyceUtil import spyceException, spyceModule ################################################## # Spyce tag library # def invokeSingleton(api, tagname, memoize=False, **kwargs): assert isinstance(api, spyce.spyceWrapper), 'expected spyceWrapper; got %s' % api.__class__ spylambda = api.getModule('spylambda') code = '[[ from spyceException import * ]] <%s %s />' % (tagname, ' '.join(['%s="=%s"' % (key, value) for key, value in kwargs.iteritems()])) lamb = spylambda.define(','.join(kwargs.iterkeys()), code, memoize) lamb(**kwargs) class spyceTagLibrary: "All Spyce tag libraries should subclass this." def __init__(self, prefix): self._prefix = prefix self._taghash = {} for tag in self.tags: self._taghash[tag.name] = tag def getTag(self, api, name, id, context, attrs, paired, parent=None): tag = self.getTagClass(name)(id, api, self._prefix, context, attrs, paired, parent) tag._lib = self return tag def getTagClass(self, name): return self._taghash[name] # functions to override tags = [] def start(self): pass def finish(self): pass ################################################## # Spyce tag # class spyceTag: "All Spyce tags should subclass this." def __init__(self, id, api, prefix, context, attrs, paired, parent=None): "Initialize a tag; prefix = current library prefix" self._id = id if 0 and api: assert isinstance(api, spyce.spyceWrapper) self._api = api self._prefix = prefix self._pair = paired self._parent = parent self._context = context self._out = None self._buffered = 0 if api and 'handler' in attrs: # api == None means it's just tagchecker, and parent will always be None ftag = self.getParent('form') if not ftag: raise spyceTagSyntaxException('tags with active handlers must be nested inside form:form') if 'action' in ftag._attrs: raise spyceTagSyntaxException('parent form action is incompatible with active handlers') # delete it before it gets to begin() # subclass can always do own attr checking before calling spyceTag.__init__ del attrs['handler'] self._attrs = self._attrsEval(attrs) def _attrsEval(self, attrs): if not self._context: # tagchecker passes None return attrs for key, expr in attrs.items(): if expr and expr[0] == '=': attrs[key] = self.eval(expr[1:]) return attrs def eval(self, expr): try: return eval(expr, self._context) except NameError, e: if '.' not in expr: raise # maybe it's a module reference modname = expr.split('.')[0] try: mod = self._api.getModule(modname) except ImportError: raise e self._context[modname] = mod return eval(expr, self._context) except TypeError: raise 'Expected python code; unable to evaluate %s' % expr # setup tag environment (output stream) def setOut(self, out): "Set output stream" self._out = out def setBuffered(self, buffered): "Set whether tag is running on a buffer wrt. enclosing scope" self._buffered = buffered # accessors def getPrefix(self): "Return tag prefix" return self._prefix def getAttributes(self): "Get tag attributes." return self._attrs def getPaired(self): "Return whether this is a paired or singleton tag." return self._pair def getParent(self, name=None): "Get parent tag" parent = self._parent if name is not None: while parent is not None: if parent.name == name: break parent = parent._parent return parent def getFullId(self): id = '' p = self # tag compiler restricts handlers to be inside form while p and p.__class__.__name__ != 'form_form': if p._id: id = p._id + id p = p._parent return id def getOut(self): "Return output stream" return self._out def getBuffered(self): "Get whether tag is running on a buffer wrt. enclosing scope" return self._buffered # functions and fields to override "Code chunk to insert into calling class (code, ref)" # for use by [[! code blocks in compiled tags classcode = None "Handlers to insert into calling class" handlers = None "The name of this tag!" name = None "Whether this tag wants to buffer its body processing" buffer = 0 "Whether this tag want to conditionally perform body processing" conditional = 0 "Whether this tag wants to possibly loop body processing" loops = 0 "Whether this tag wants to handle exceptions" catches = 0 "Whether end() must (even on exception) get called if begin() completes" mustend = 0 "Whether this tag wants to export values to calling context (overrides export())" exports = 0 def syntax(self): "Check tag syntax" pass def begin(self, **kwargs): "Process start tag; return true to process body (if conditional==1)" return 1 def export(self): """ return dict of any key/value pairs tag wants to propagate into spyceProcess locals. (Obviously, key must be a string; value may be any object.) Used e.g. by to send x into parent scope. _Must set class.exports or export method will not be called._ """ return None def body(self, _contents): "Process tag body; return true to repeat (if loops==1)" if _contents: self.getOut().write(_contents) return 0 def end(self): "Process end tag" pass def catch(self, ex): "Process any exception thrown by tag (if catches==1)" raise class spyceTagPlus(spyceTag): "An easier spyceTag class to work with..." def getModule(self, name): "Return a Spyce module reference" return self._api.getModule(name) def parentRequired(self, parentname): parent = self.getParent(parentname) if not parent: raise '%s tag must be used inside a parent %s active tag' % (self.name, parentname) return parent def syntaxNonEmpty(self, *names): for name in names: try: value = self._attrs[name] except KeyError: return if not value: raise spyceTagSyntaxException('attribute "%s" should not be empty', name) def syntaxNonEmpty(self, *names): for name in names: try: value = self._attrs[name] except KeyError: return if not value: raise spyceTagSyntaxException('attribute "%s" should not be empty', name) def syntaxNonEmpty(self, *names): for name in names: try: value = self._attrs[name] except KeyError: return if not value: raise spyceTagSyntaxException('attribute "%s" should not be empty', name) def syntaxNonEmpty(self, *names): for name in names: try: value = self._attrs[name] except KeyError: return if not value: raise spyceTagSyntaxException('attribute "%s" should not be empty', name) def syntaxNonEmpty(self, *names): for name in names: try: value = self._attrs[name] except KeyError: return if not value: raise spyceTagSyntaxException('attribute "%s" should not be empty', name) def syntaxValidSet(self, name, validSet): try: value = self._attrs[name] except KeyError: return if value not in validSet: raise spyceTagSyntaxException('attribute "%s" should be one of: %s'% (name, ', '.join(validSet))) def syntaxPairOnly(self): "Ensure that this tag is paired i.e. open/close" if not self._pair: raise spyceTagSyntaxException('singleton tag not allowed') def syntaxSingleOnly(self): "Ensure that this tag is single i.e. " if self._pair: raise spyceTagSyntaxException('paired tag not allowed') ################################################## # Spyce tag syntax checking # class spyceTagChecker: def __init__(self, server): self._server = server self._taglibs = {} self._stack = [] def loadLib(self, libname, libfrom, libas, rel_file, info=None): if not libas: libas = libname try: self._taglibs[(libname, libfrom)] = \ self._server.loadModule(libname, libfrom, rel_file)(libas) except (SyntaxError, TypeError): raise except: sys.stdout.write('%s\n' % spyceUtil.exceptionString()) raise spyceException.spyceSyntaxError( 'unable to load module: %s (%s)' % (libname, libas), info) def getTag(self, (libname,libfrom), name, context, attrs, pair, info): lib = self._taglibs[(libname, libfrom)] try: return lib.getTag(None, name, None, None, attrs, pair, None) except: spyce.DEBUG(spyceUtil.exceptionString()) raise spyceException.spyceSyntaxError( 'unknown tag "%s:%s"'%(libname, name), info) def getTagClass(self, (libname, libfrom), name, info): lib = self._taglibs[(libname, libfrom)] try: return lib.getTagClass(name) except: spyce.DEBUG(spyceUtil.exceptionString()) s = 'unknown tag "%s:%s" (known tags in %s are: %s)' % ( libname, name, libname, ','.join([t.name for t in lib.tags])) raise spyceException.spyceSyntaxError(s, info) def startTag(self, (libname,libfrom), name, attrs, pair, info): tag = self.getTag((libname, libfrom), name, None, attrs, pair, info) try: # standard signature validation (args, varargs, varkw, defaults) = inspect.getargspec(tag.begin) # args w/ defaults don't need to be checked if defaults: n_defaults = len(defaults) else: n_defaults = 0 L = args[1:len(args) - n_defaults] # assume self is first for attr in L: if attr not in attrs: raise spyceTagSyntaxException('"%s" tag call missing compulsory "%s" attribute' % (name, attr)) # extra attrs cause an error, if *args/**kwargs not in signature for attr in attrs: # spyceCompile checks to make sure 'handler' is ok; tagChecker # doesn't have the necessary info to make that decision. if attr not in args and not varargs and not varkw and attr != 'handler': raise spyceTagSyntaxException('unexpected attribute "%s"' % attr) # custom validation error = tag.syntax() except spyceTagSyntaxException, e: spyce.DEBUG(spyceUtil.exceptionString()) raise spyceException.spyceSyntaxError(str(e), info) if error: raise spyceException.spyceSyntaxError(error, info) if pair: self._stack.append( (libname, libfrom, name, info) ) def endTag(self, (libname,libfrom), name, info): try: libname1, libfrom1, name1, info1 = self._stack.pop() except IndexError: raise spyceException.spyceSyntaxError( 'unmatched close tag', info) if (libname1,libfrom1,name1) != (libname,libfrom,name): raise spyceException.spyceSyntaxError( 'unmatched close tag, expected <%s:%s>' % (libname1,name1), info) def finish(self): if self._stack: libname, libfrom, name, info = self._stack.pop() raise spyceException.spyceSyntaxError( 'unmatched open tag', info) ################################################## # Spyce tag syntax exception # class spyceTagSyntaxException: def __init__(self, str): self._str = str def __repr__(self): return self._str