def __init__(self):
        from tayra.parser import TTLParser
        from tayra.codegen import InstrGen
        from tayra.runtime import StackMachine

        self.ttlparser = TTLParser(self)
        self.igen = InstrGen(self)
        self.mach = StackMachine(self)  # Stack machine
Example #2
0
 def __init__( self ):
     from tayra.parser   import TTLParser
     from tayra.codegen  import InstrGen
     from tayra.runtime  import StackMachine
     
     self.ttlparser = TTLParser( self )
     self.igen = InstrGen( self )
     self.mach = StackMachine( self ) # Stack machine
Example #3
0
class TTLCompiler( Plugin ):
    """Tayra compiler. Implemented as plugin to leverage on pluggdapps
    configuration system. Also implement :class:`ITemplate`. Creating a
    plugin instance can be a costly operation, to avoid this instantiate this
    plugin once and use :meth:`_init` to initialize it for subsequent uses.
    """

    implements( ITemplate )

    _memcache = {}

    ttlloc = None
    """TemplateLookup object. Encapsulates location information for template
    file and its intermediate python file."""

    ttlfile = ''
    """Absolute file path pointing to .ttl template script file in."""

    ttltext = ''
    """String of template script either read from the template file or
    received directly."""

    pyfile = ''
    """If present, absolute file path pointing to compiled template script,
    .py file."""

    pytext = ''
    """String of python script translated from template script."""

    encoding = ''
    """Character encoding of original template script file."""

    ttlparser = None
    """:class:`tayra.parser.TTLParser` object."""

    igen = None
    """:class:`tayra.codegen.InstrGen` object."""

    mach = None
    """:class:`tayra.runtime.StackMachine` object."""

    def __init__( self ):
        from tayra.parser   import TTLParser
        from tayra.codegen  import InstrGen
        from tayra.runtime  import StackMachine
        
        self.ttlparser = TTLParser( self )
        self.igen = InstrGen( self )
        self.mach = StackMachine( self ) # Stack machine

    def __call__( self, **kwargs ):
        """Clone a plugin with the same settings as this one. settings can
        also be overriden by passing ``settings`` key-word argument as a
        dictionary."""
        settings = {}
        settings.update({ k : self[k] for k in self })
        settings.update( kwargs.pop( 'settings', {} ))
        kwargs['settings'] = settings
        return self.qp( ISettings, 'tayra.ttlcompiler', **kwargs )

    def _init( self, file=None, text=None ):
        """Reinitialize the compiler object to compile a different template
        script."""
        from pluggdapps import papackages
        self.ttlloc = TemplateLookup( self, file, text )
        self.encoding = self.ttlloc.encoding
        self.ttlfile = self.ttlloc.ttlfile
        self.ttltext = self.ttlloc.ttltext
        self.pyfile = self.ttlloc.pyfile
        
        # Compute the module name from ttlfile.
        asset = h.asset_spec_from_abspath( self.ttlfile, papackages )
        if asset :
            n = '.'.join(asset.split(':', 1)[1].split( os.path.sep ))
            self.modulename = n[:-4] if n.endswith('.ttl') else n
        else :
            n = '.'.join(self.ttlfile.split( os.path.sep ))
            self.modulename = n[:-4] if n.endswith('.ttl') else n

        self.mach._init( self.ttlfile )
        self.igen._init()

        # Check whether pyfile is older than the ttl file. In debug mode is is
        # always re-generated.
        self.pytext = ''
        if self['debug'] == False :
            if self.ttlfile and isfile( self.ttlfile ) :
                if self.pyfile and isfile( self.pyfile ) :
                    m1 = os.stat( self.ttlfile ).st_mtime
                    m2 = os.stat( self.pyfile ).st_mtime
                    if m1 <= m2 :
                        self.pytext = open( self.pyfile ).read()

    def toast( self, ttltext=None ):
        """Convert template text into abstract-syntax-tree. Return the root
        node :class:`tayra.ast.Template`."""
        ttltext = ttltext or self.ttltext
        self.ast = self.ttlparser.parse( ttltext, ttlfile=self.ttlfile )
        return self.ast

    def topy( self, ast, *args, **kwargs ):
        """Generate intermediate python text from ``ast`` obtained from
        :meth:`toast` method. If configuration settings allow for persisting
        the generated python text, then this method will save the intermediate
        python text in file pointed by :attr:`pyfile`."""
        ast.validate()
        ast.headpass1( self.igen )                   # Head pass, phase 1
        ast.headpass2( self.igen )                   # Head pass, phase 2
        ast.generate( self.igen, *args, **kwargs )   # Generation
        ast.tailpass( self.igen )                    # Tail pass
        pytext = self.igen.codetext()
        if self.pyfile and isdir( dirname( self.pyfile )) :
            open( self.pyfile, 'w', encoding='utf-8' ).write( pytext )
        return pytext

    def compilettl( self, file=None, text=None, args=[], kwargs={} ):
        """Translate a template script text or template script file into 
        intermediate python text. Compile the python text and return the code
        object. If ``memcache`` configuration is enable, compile code is
        cached in memory using a hash value generated from template text.
        
        ``args`` and ``kwargs``,
            position arguments and keyword arguments to use during
            AST.generate() pass.
        """
        self._init( file=file, text=text ) if (file or text) else None
        code = self._memcache.get( self.ttlloc.ttlhash, None )
        if not code :
            if not self.pytext :
                self.pytext = self.topy( self.toast(), *args, **kwargs )
            code = compile( self.pytext, self.pyfile, 'exec' )
        if self['memcache'] :
            self._memcache.setdefault( self.ttlloc.ttlhash, code )
        return code

    def load( self, code, context={} ):
        """Using an optional ``context``, a dictionary of key-value pairs, and
        the code object obtained from :meth:`compilettl` method, create a new
        module instance populating it with ``context`` and some standard
        references."""
        from   tayra.runtime import Namespace
        import tayra.h       as tmplh

        # Module instance for the ttl file
        module = imp.new_module( self.modulename )

        # Create helper module
        helper = imp.new_module( 'template_helper' )
        filterfn = lambda k, v : callable(v)
        [ helper.__dict__.update( pynamespace(m) ) 
                    for m in [tmplh] + self['helpers'] ]
        ctxt = {
            self.igen.machname : self.mach,
            '_compiler'   : self,
            'this'        : Namespace( None, module ),
            'local'       : module,
            'parent'      : None,
            'next'        : None,
            'h'           : helper,
            '__file__'    : self.pyfile,
            '_ttlfile'    : self.ttlfile,
            '_ttlhash'    : self.ttlloc.ttlhash,
        }
        ctxt.update( context )
        ctxt['_context'] = ctxt
        module.__dict__.update( ctxt )
        # Execute the code in module's context
        sys.modules.setdefault( self.modulename, module )
        exec( code, module.__dict__, module.__dict__ )
        return module

    def generatehtml( self, module, context={} ):
        """Using the ``module`` object obtained from :meth:`load` method, and
        a context dictionary, call the template module's entry point to 
        generate HTMl text and return the same.
        """
        try :
            entry = getattr( module.this, self['entry_function'] )
            args = context.get( '_bodyargs', [] )
            kwargs = context.get( '_bodykwargs', {} )
            html = entry( *args, **kwargs ) if callable( entry ) else ''
            try :
                from bs4 import BeautifulSoup
                if self['beautify_html'] :
                    html = BeautifulSoup( html ).prettify()
            except :
                pass
            return html
        except :
            if self['debug'] : raise
            return ''

    def importlib(self, this, context, file):
        """Import library ttl files inside the main script's context"""
        compiler = self()
        context['_compiler'] = compiler
        context['this'] = this
        return compiler.load( compiler.compilettl(file=file), context=context )
        

    #---- ITemplate interface methods

    def render( self, context, **kwargs ):
        """:meth:`pluggdapps.interfaces.ITemplate.render` interface 
        method. Generate HTML string from template script passed either via 
        ``ttext`` or via ``tfile``.

        ``tfile``,
            Location of Tayra template file, either as relative directory or as
            asset specification.

        ``ttext``,
            Tayra template text string.
        """
        file, text = kwargs.get('file', None), kwargs.get('text', None)
        code = self.compilettl( file=file, text=text )
        module = self.load( code, context=context )
        html = self.generatehtml( module, context )
        return html

    #---- ISettings interface methods

    @classmethod
    def default_settings( cls ):
        """:meth:`pluggdapps.plugin.ISettings.default_settings` interface 
        method."""
        return _defaultsettings

    @classmethod
    def normalize_settings( cls, sett ):
        """:meth:`pluggdapps.plugin.ISettings.normalize_settings` interface 
        method."""
        sett['nocache'] = h.asbool( sett['nocache'] )
        sett['optimize'] = h.asint( sett['optimize'] )
        sett['lex_debug'] = h.asint( sett['lex_debug'] )
        sett['yacc_debug'] = h.asint( sett['yacc_debug'] )
        sett['strict_undefined'] = h.asbool( sett['strict_undefined'] )
        sett['directories'] = h.parsecsvlines( sett['directories'] )
        sett['tag.plugins'] = h.parsecsvlines( sett['tag.plugins'] )
        sett['beautify_html'] = h.asbool( sett['beautify_html'] )
        sett['memcache'] = h.asbool( sett['memcache'] )
        sett['helpers'] = h.parsecsv( sett['helpers'] )
        return sett
class TTLCompiler(Plugin):
    """Tayra compiler. Implemented as plugin to leverage on pluggdapps
    configuration system. Also implement :class:`ITemplate`. Creating a
    plugin instance can be a costly operation, to avoid this instantiate this
    plugin once and use :meth:`_init` to initialize it for subsequent uses.
    """

    implements(ITemplate)

    _memcache = {}

    ttlloc = None
    """TemplateLookup object. Encapsulates location information for template
    file and its intermediate python file."""

    ttlfile = ''
    """Absolute file path pointing to .ttl template script file in."""

    ttltext = ''
    """String of template script either read from the template file or
    received directly."""

    pyfile = ''
    """If present, absolute file path pointing to compiled template script,
    .py file."""

    pytext = ''
    """String of python script translated from template script."""

    encoding = ''
    """Character encoding of original template script file."""

    ttlparser = None
    """:class:`tayra.parser.TTLParser` object."""

    igen = None
    """:class:`tayra.codegen.InstrGen` object."""

    mach = None
    """:class:`tayra.runtime.StackMachine` object."""
    def __init__(self):
        from tayra.parser import TTLParser
        from tayra.codegen import InstrGen
        from tayra.runtime import StackMachine

        self.ttlparser = TTLParser(self)
        self.igen = InstrGen(self)
        self.mach = StackMachine(self)  # Stack machine

    def __call__(self, **kwargs):
        """Clone a plugin with the same settings as this one. settings can
        also be overriden by passing ``settings`` key-word argument as a
        dictionary."""
        settings = {}
        settings.update({k: self[k] for k in self})
        settings.update(kwargs.pop('settings', {}))
        kwargs['settings'] = settings
        return self.qp(ISettings, 'tayra.ttlcompiler', **kwargs)

    def _init(self, file=None, text=None):
        """Reinitialize the compiler object to compile a different template
        script."""
        from pluggdapps import papackages
        self.ttlloc = TemplateLookup(self, file, text)
        self.encoding = self.ttlloc.encoding
        self.ttlfile = self.ttlloc.ttlfile
        self.ttltext = self.ttlloc.ttltext
        self.pyfile = self.ttlloc.pyfile

        # Compute the module name from ttlfile.
        asset = h.asset_spec_from_abspath(self.ttlfile, papackages)
        if asset:
            n = '.'.join(asset.split(':', 1)[1].split(os.path.sep))
            self.modulename = n[:-4] if n.endswith('.ttl') else n
        else:
            n = '.'.join(self.ttlfile.split(os.path.sep))
            self.modulename = n[:-4] if n.endswith('.ttl') else n

        self.mach._init(self.ttlfile)
        self.igen._init()

        # Check whether pyfile is older than the ttl file. In debug mode is is
        # always re-generated.
        self.pytext = ''
        if self['debug'] == False:
            if self.ttlfile and isfile(self.ttlfile):
                if self.pyfile and isfile(self.pyfile):
                    m1 = os.stat(self.ttlfile).st_mtime
                    m2 = os.stat(self.pyfile).st_mtime
                    if m1 <= m2:
                        self.pytext = open(self.pyfile).read()

    def toast(self, ttltext=None):
        """Convert template text into abstract-syntax-tree. Return the root
        node :class:`tayra.ast.Template`."""
        ttltext = ttltext or self.ttltext
        self.ast = self.ttlparser.parse(ttltext, ttlfile=self.ttlfile)
        return self.ast

    def topy(self, ast, *args, **kwargs):
        """Generate intermediate python text from ``ast`` obtained from
        :meth:`toast` method. If configuration settings allow for persisting
        the generated python text, then this method will save the intermediate
        python text in file pointed by :attr:`pyfile`."""
        ast.validate()
        ast.headpass1(self.igen)  # Head pass, phase 1
        ast.headpass2(self.igen)  # Head pass, phase 2
        ast.generate(self.igen, *args, **kwargs)  # Generation
        ast.tailpass(self.igen)  # Tail pass
        pytext = self.igen.codetext()
        if self.pyfile and isdir(dirname(self.pyfile)):
            open(self.pyfile, 'w', encoding='utf-8').write(pytext)
        return pytext

    def compilettl(self, file=None, text=None, args=[], kwargs={}):
        """Translate a template script text or template script file into 
        intermediate python text. Compile the python text and return the code
        object. If ``memcache`` configuration is enable, compile code is
        cached in memory using a hash value generated from template text.
        
        ``args`` and ``kwargs``,
            position arguments and keyword arguments to use during
            AST.generate() pass.
        """
        self._init(file=file, text=text) if (file or text) else None
        code = self._memcache.get(self.ttlloc.ttlhash, None)
        if not code:
            if not self.pytext:
                self.pytext = self.topy(self.toast(), *args, **kwargs)
            code = compile(self.pytext, self.pyfile, 'exec')
        if self['memcache']:
            self._memcache.setdefault(self.ttlloc.ttlhash, code)
        return code

    def load(self, code, context={}):
        """Using an optional ``context``, a dictionary of key-value pairs, and
        the code object obtained from :meth:`compilettl` method, create a new
        module instance populating it with ``context`` and some standard
        references."""
        from tayra.runtime import Namespace
        import tayra.h as tmplh

        # Module instance for the ttl file
        module = imp.new_module(self.modulename)

        # Create helper module
        helper = imp.new_module('template_helper')
        filterfn = lambda k, v: callable(v)
        [
            helper.__dict__.update(pynamespace(m))
            for m in [tmplh] + self['helpers']
        ]
        ctxt = {
            self.igen.machname: self.mach,
            '_compiler': self,
            'this': Namespace(None, module),
            'local': module,
            'parent': None,
            'next': None,
            'h': helper,
            '__file__': self.pyfile,
            '_ttlfile': self.ttlfile,
            '_ttlhash': self.ttlloc.ttlhash,
        }
        ctxt.update(context)
        ctxt['_context'] = ctxt
        module.__dict__.update(ctxt)
        # Execute the code in module's context
        sys.modules.setdefault(self.modulename, module)
        exec(code, module.__dict__, module.__dict__)
        return module

    def generatehtml(self, module, context={}):
        """Using the ``module`` object obtained from :meth:`load` method, and
        a context dictionary, call the template module's entry point to 
        generate HTMl text and return the same.
        """
        try:
            entry = getattr(module.this, self['entry_function'])
            args = context.get('_bodyargs', [])
            kwargs = context.get('_bodykwargs', {})
            html = entry(*args, **kwargs) if callable(entry) else ''
            try:
                from bs4 import BeautifulSoup
                if self['beautify_html']:
                    html = BeautifulSoup(html).prettify()
            except:
                pass
            return html
        except:
            if self['debug']: raise
            return ''

    def importlib(self, this, context, file):
        """Import library ttl files inside the main script's context"""
        compiler = self()
        context['_compiler'] = compiler
        context['this'] = this
        return compiler.load(compiler.compilettl(file=file), context=context)

    #---- ITemplate interface methods

    def render(self, context, **kwargs):
        """:meth:`pluggdapps.interfaces.ITemplate.render` interface 
        method. Generate HTML string from template script passed either via 
        ``ttext`` or via ``tfile``.

        ``tfile``,
            Location of Tayra template file, either as relative directory or as
            asset specification.

        ``ttext``,
            Tayra template text string.
        """
        file, text = kwargs.get('file', None), kwargs.get('text', None)
        code = self.compilettl(file=file, text=text)
        module = self.load(code, context=context)
        html = self.generatehtml(module, context)
        return html

    #---- ISettings interface methods

    @classmethod
    def default_settings(cls):
        """:meth:`pluggdapps.plugin.ISettings.default_settings` interface 
        method."""
        return _defaultsettings

    @classmethod
    def normalize_settings(cls, sett):
        """:meth:`pluggdapps.plugin.ISettings.normalize_settings` interface 
        method."""
        sett['nocache'] = h.asbool(sett['nocache'])
        sett['optimize'] = h.asint(sett['optimize'])
        sett['lex_debug'] = h.asint(sett['lex_debug'])
        sett['yacc_debug'] = h.asint(sett['yacc_debug'])
        sett['strict_undefined'] = h.asbool(sett['strict_undefined'])
        sett['directories'] = h.parsecsvlines(sett['directories'])
        sett['tag.plugins'] = h.parsecsvlines(sett['tag.plugins'])
        sett['beautify_html'] = h.asbool(sett['beautify_html'])
        sett['memcache'] = h.asbool(sett['memcache'])
        sett['helpers'] = h.parsecsv(sett['helpers'])
        return sett