PlowPlate - HTML template class

Code-Stücke können hier veröffentlicht werden.
Antworten
XT@ngel
User
Beiträge: 255
Registriert: Dienstag 6. August 2002, 14:36
Kontaktdaten:

Da es diese Klasse nicht mehr zum Download gibt poste ich sie hier rein.
Vielleicht optimiert sie jemand, damit das ganze ein bisschen schneller wird.

Code: Alles auswählen

# -*- coding: UTF-8 -*- 

"""PlowPlate -- nestable HTML template class

Version 0.3, 24-Apr-2001, by Mike Orr <iron@mso.oz.net>

License: your choice of GPL, Python or Webware.  If you modify the API and make
your changes public, please rename the module to prevent confusion.

See the README file for usage examples.
"""
import cStringIO, UserDict, getopt, os, pprint, re, sys
try:
    import NewBuiltins
except ImportError:
    True, False = (1==1), (1==0)
    def irange(lis):
        ret = []
        for i in range(len(lis)):
            tup = i, lis[i]
            ret.append(tup)
        return ret


############################################################################
# XXX These functions should be in os.path--submit bug to Python.
def find(file, path, exceptional=False):
    """Find a file in a search path.

       in : file, string, the filename,
            path, list of strings, the directories to look in.
            exceptional, boolean, true to raise IOError if file not found.
              False to return None in this case.
       out: string, the first file found (directory + filename).
       exc: IOError if file not found and 'exceptional' is true.
    """
    hits = find_all(file, path)
    if   len(hits) >= 1:
        return hits[0]
    elif exceptional:
        raise IOError("file %s not found in path %s" % (`file`, `path`) )
    else:
        return None

        

def find_all(file, path):
    """Find all occurrances of a file in a search path.

       in : file, string, the filename,
            path, list of strings, the directories to look in.
       out: list of strings, all files found (directory + filename).  Returns
              the empty list if none found.
    """
    ret = []
    for dir in path:
        p = os.path.join(dir, file)
        if os.path.exists(p):
            ret.append(p)
    return ret
############################################################################

def _sln(s):
    """Strip one leading newline off a string.  Returns the string unchanged
       if there's no leading newline.
    """
    if s[0:1] == '\n':
        s = s[1:]
    return s

PLACEHOLDER_PAT = r"%s([\w-]*?)%s"
BLOCK_PAT = r"(?is)^(?P<before>.*)<!-- BEGIN %s -->(?P<block>.*?)<!-- END %s -->(?P<after>.*)$"
    # "(?is)" means dotall and ignorecase.


class PlowPlate(UserDict.UserDict):
    """See the README file for usage examples.

       Class attributes:
       _left, string, the opening delimeter of a placeholder key.
       _right, string, the closing delimeter of a placeholder key.
       _path, list of strings, directories to search in for file templates.

       Instance attributes:
       _template, string, the template.
       _components, list of strings, The components of the template.  Each even
          subscript (starting with 0) is a literal string to be output.  Each
          odd subscript (starting with 1) is a placeholder key to be
          substituted in the output.
       _parsedTemplate, string, the template as it was when '_components' was 
          built.  Used to verify whether '_template' has changed since
          '_components' was last built.  Default is None so that it will never
          match '_template' the first time around.
       _rows, list of strings, the result of each call to row().  Used only
         inside for-blocks.

       Rules:
       ** Any method that uses '_components' must call parseTemplate() first
       to ensure '_components' has kept up with any changes to '_template'.
    """
    _left = "{"
    _right = "}"
    _path = [""]
    
    def __init__(self, template, isFile=True, dic={}): 
        """Constructor.

           in : template, string, the template filename or the template itself.
                isFile, boolean, true if 'template' is a filename, false if it's
                  a literal string.
                dic, dictionary, initial plug-in values.
           out: None.
        """
        UserDict.UserDict.__init__(self, dic)
        if isFile:
            self.file(template)
        else:
            self._template = template
        self._components = None
        self._parsedTemplate = None
        self._rows = []


    def file(self, file):
        """Read the template from a file.
        
           in : file, string, the filename.
           out: None.
           act: Replaces the previous template.
           exc: IOError if the file is not found or cannot be read.
        """
        fullpath = find(file, self._path, 1) # Raises IOError.
        f = open(fullpath) # Raises IOError.
        self._template = f.read()
        f.close()


    def __str__(self):
        """Performs the substitution.
        """
        if self._rows:
            return "".join(self._rows)
        else:
            return self.sub()


    def parseTemplate(self, force=False):
        """Private method to (re)build '_components' from '_template'.
        
           in : force, boolean, true to perform the work even if it looks like
                  it's already been done.
           out: None.
        """
        if (self._template is self._parsedTemplate) and not force:
            return
        components = []
        left = re.escape(self._left)
        right = re.escape(self._right)
        pat = PLACEHOLDER_PAT % (left, right) 
        rx = re.compile(pat)
        template = self._template
        pos = 0
        while 1:
            match = rx.search(template, pos)
            if match is None:
                components.append(template[pos:])
                break
            else:
                s = template[pos:match.start(0)]
                components.append(s)
                components.append( match.group(1) )
                pos = match.end(0)
        self._components = components
        self._parsedTemplate = template


    def clearRows(self):
        """Throws away any rows that have been generated by row().  Call this
           if you want to reuse the same template with a different set of rows,
           or to use it again as an ordinary template after row() has been
           called.  This method is useful only in objects that are for-blocks.
        """
        self._rows = []


    def findBlock(self, blockName):
        """Finds a block in the template (without modifying the template).

           in : blockName, string, the name of the block to search for.
           out: tuple: 
                  [0], string, the part of the template before the opening
                         block delimeter.
                  [1], string, the block body.
                  [2], string, the part of the template after the closing block
                         delimeter.
           note: The return value does not include the block delimeters or the
                   block name.
           exc: ValueError, if the block is not found.
        """
        pat = BLOCK_PAT % (blockName, blockName)
        rx = re.compile(pat)
        match = rx.search(self._template)
        if match is None:
                raise ValueError("blockName '%s' not found" % block)
        before = match.group('before')
        block = _sln( match.group('block') )
        after = _sln( match.group('after') )
        return before, block, after

    
    def block(self, blockName):
        """Extracts a block into a separate PlowPlate instance.

           in : blockName, string, the name of the block.
           out: PlowPlate, an instance whose template is the block body.
           act: Replaces the block in the template with a placeholder key to it.
                Adds a plug-in of blockName : the new PlowPlate instance.
                Invalidates '_parsedTemplate' so that '_components' will be
                rebuilt the next time it is used.  See the README file if you
                don't understand why all this happens.
        """
        before, block, after = self.findBlock(blockName)
        sio = cStringIO.StringIO()
        sio.write(before)
        sio.write(self._left)
        sio.write(blockName)
        sio.write(self._right)
        sio.write(after)
        self._template = sio.getvalue()
        self._parsedTemplate = None
        sio.close()
        o = PlowPlate(block, False)
        self[blockName] = o
        return o

    
    def keep(self, blockName, keep=True):
        """Handles an if-block in an optimistic manner.

           in : blockName, string, name of the block to find.
                keep, boolean, true to modify the template to remove the block
                  markers but keep the block body.  False to remove the block
                  body also.
           out: None.
           note: See the README file if you don't understand what this is for.
        """
        before, block, after = self.findBlock(blockName)
        tup = keep and (before, block, after) or (before, after)
        self._template = "".join(tup)


    def kill(self, blockName, kill=True):
        """Handle an if-block in a pessemistic manner.

           Same as keep() but reverses the sense of the boolean expression.
        """
        self.keep(blockName, not kill)


    def distribution(self):
        """Shows which placeholders occur the most frequently in the template.

           in : none.
           out: dictionary, placeholder key : number of occurrences.
           note: This method is just an extra and is not needed for ordinary
                   template use.
        """
        self.parseTemplate()
        components = self._components
        dic = {}
        for i in range(1, len(components), 2):
            key = components[i]
            if dic.has_key(key):
                dic[key] = dic[key] + 1
                # "dic[key] += 1" raises syntax error. :(
            else:
                dic[key] = 1
        return dic

    
    def placeholders(self):
        """Returns a list of all the unique placeholder keys in the template.

           in : none.
           out: list of strings, the placeholder keys.
           note: This method is useful for debugging your template, but is not
                   needed for ordinary template use.
        """
        distrib = self.distribution()
        ret = distrib.keys()
        #ret.sort()
        return ret


    def unknowns(self):
        """Returns a list of all the unknown placeholder keys in the template.

           in : none.
           out: list of strings, the placeholder keys which do not have a
                  corresponding key in the plug-in dictionary.
           note: This method is for debugging your template and is not needed
                   for ordinary template use.
           """
        ret = []
        for key in self.placeholders():
            if self.has_key(key):
                ret.append(key)
        return ret
        
    
    def sub(self):
        """Perform the substitutions and return the result (without modifying
           anything).

           in : none.
           out: string, the result of performing the substitions on the 
                  template.
        """
        self.parseTemplate()
        sio = cStringIO.StringIO()
        for i, component in irange(self._components):
            if   i % 2 == 0: # If subscript is even
                subst = component
            else:
                subst = self.valueForKey(component)
            subst = str(subst)
            sio.write(subst)
        ret = sio.getvalue()
        sio.close()
        return ret


    def valueForKey(self, key):
        """Returns the plug-in value mapped to the given key.  If the key is
           not registered, calls valueForUnknownKey and returns the result.
        """
        if self.has_key(key):
            return self[key]
        else:
            return self.valueForUnknownKey(key)


    def valueForUnknownKey(self, key):
        """Returns a default substitution for a key which (presumably) does not
           have an associated plug-in value.  This implementation returns the
           key with its surrounding placeholders re-added.  When placed in the
           output stream, this recreates the placeholder as it was in the
           template.

           Possible overrides you may want to implement:
           ** Return the empty string to squeeze out unknown placeholders.
           ** Return an HTML comment or error message to insert that into he
              output stream.
           ** Raise KeyError to halt the program dead in its tracks.
        """
        return self._left + key + self._right


    def row(self):
        """Performs a substitution that is one row in a for-block.

           in : none.
           out: None.
           act: Appends the result to '_rows'.  Later when you call __str__(),
                all the generated rows will be returned.
           note: See the README file for usage examples.  This method is useful
                only in instances which are for-block bodies.
        """
        s = self.sub()
        self._rows.append(s)


############### MAIN ROUTINE #########################################

def main():
    """See usage string at bottom of function."""
    class UsageError(Exception):  
        pass
    err_term = "Cowardly refusing to read standard input from a terminal."
    err_argc = "Wrong number of command-line arguments."
    try:
        print_placeholders_only = False
        opts, args = getopt.getopt(sys.argv[1:], 'kp')
        for o, a in opts:
            if o in ("-k", "-p"):
                print_placeholders_only = True
        args_len = len(args)
        if   args_len == 0:
            if sys.stdin.isatty():
                raise UsageError(err_term)
            template = sys.stdin.read()
            p = Plow(template, False)
        elif args_len == 1:
            file = args[0]
            p = PlowPlate(file)
        else:
            raise UsageError(err_argc)
        if print_placeholders_only:
            for ph in p.placeholders():
                print ph
        else:
            report = p.distribution()
            pprint.pprint(report)
            
    except (UsageError, getopt.error), reason:
        if reason:
            sys.stderr.write("%s\n" % reason)
        progname = sys.argv[0]
        sys.stderr.write("""\
Usage: %s [-kp] filename
  With -k or -p: prints the unique placeholders (=keys), one per line.
  Without -k or -p: prints a dictionary of how many times each placeholder occurs.
  'filename' is the file containing the template.
  Placeholders inside blocks are recognized, but the blocks themselves are not.
""" % progname)
        sys.exit(1)
    
    

if __name__ == "__main__":  main()


Antworten