Donate Bitcoins

Source code for bulbs.groovy

import os
import re
import string
import sre_parse
import sre_compile
from collections import OrderedDict, namedtuple
from sre_constants import BRANCH, SUBPATTERN
import hashlib
from . import utils

# GroovyScripts is the only public class

#
# The scanner code came from the TED project.
#

# TODO: Simplify this. You don't need group pattern detection.



Method = namedtuple('Method', ['definition', 'signature', 'body', 'sha1'])

class LastUpdatedOrderedDict(OrderedDict):
    """Store items in the order the keys were last added."""

    def __setitem__(self, key, value):
        if key in self:
            del self[key]
        OrderedDict.__setitem__(self, key, value)


[docs]class GroovyScripts(object): """ Store and manage an index of Gremlin-Groovy scripts. :parm config: Config object. :type config: bulbs.Config :param file_path: Path to the base Groovy scripts file. :type file_path: str :ivar config: Config object. :ivar source_files: List containing the absolute paths to the script files, in the order they were added. :ivar methods: LastUpdatedOrderedDict mapping Groovy method names to the Python Method object, which is a namedtuple containing the Groovy script's definition, signature, body, and sha1. .. note:: Use the update() method to add subsequent script files. Order matters. Groovy methods are overridden if subsequently added files contain the same method name as a previously added file. """ #: Relative path to the default script file default_file = "gremlin.groovy" def __init__(self, config, file_path=None): self.config = config self.source_file_map = OrderedDict() # source_file_map[file_path] = namespace # may have model-specific namespaces # methods format: methods[method_name] = method_object self.namespace_map = OrderedDict() # namespace_map[namespace] = methods if file_path is None: file_path = self._get_default_file() # default_namespace is derifed from the default_file so # default_namespace will be "gremlin" assuming you don't change default_file # or override default_file by passing in an explicit file_path self.default_namespace = self._get_filename(file_path) self.update(file_path, self.default_namespace)
[docs] def get(self, method_name, namespace=None): """ Returns the Groovy script with the method name. :param method_name: Method name of a Groovy script. :type method_name: str :rtype: str """ # Example: my_method # uses default_namespace # my_method, my_namespace # pass in namespace as an arg # my_namespace:my_method # pass in namespace via a method_name prefix method = self.get_method(method_name, namespace) script = method.signature if self.config.server_scripts is True else method.body #script = self._build_script(method_definition, method_signature) return script
def get_methods(self, namespace): return self.namespace_map[namespace]
[docs] def get_method(self, method_name, namespace=None): """ Returns a Python namedtuple for the Groovy script with the method name. :param method_name: Name of a Groovy method. :type method_name: str :rtype: bulbs.groovy.Method """ namespace, method_name = self._get_namespace_and_method_name(method_name, namespace) methods = self.get_methods(namespace) return methods[method_name]
[docs] def update(self, file_path, namespace=None): """ Updates the script index with the Groovy methods in the script file. :rtype: None """ file_path = os.path.abspath(file_path) methods = self._get_methods(file_path) if namespace is None: namespace = self._get_filename(file_path) self._maybe_create_namespace(namespace) self.source_file_map[file_path] = namespace self.namespace_map[namespace].update(methods)
[docs] def refresh(self): """ Refreshes the script index by re-reading the Groovy source files. :rtype: None """ for file_path in self.source_file_map: namespace = self.source_file_map[file_path] methods = self._get_methods(file_path) self.namespace_map[namespace].update(methods)
def _maybe_create_namespace(self, namespace): if namespace not in self.namespace_map: methods = LastUpdatedOrderedDict() self.namespace_map[namespace] = methods def _get_filename(self, file_path): base_name = os.path.basename(file_path) file_name, file_ext = os.path.splitext(base_name) return file_name def _get_namespace_and_method_name(self, method_name, namespace=None): if namespace is None: namespace = self.default_namespace parts = string.split(method_name, ":") if len(parts) == 2: # a namespace explicitly set in method_name takes precedent namespace = parts[0] method_name = parts[1] return namespace, method_name def _get_methods(self,file_path): return Parser(file_path).get_methods() def _get_default_file(self): file_path = utils.get_file_path(__file__, self.default_file) return file_path def _build_script(definition, signature): # This method isn't be used right now... # This method is not current (rework it to suit needs). script = """ try { current_sha1 = methods[name] } catch(e) { current_sha1 = null methods = [:] methods[name] = sha1 } if (current_sha1 == sha1) %s try { return %s } catch(e) { return %s }""" % (signature, definition, signature) return script
class Scanner:
    def __init__(self, lexicon, flags=0):
        self.lexicon = lexicon
        self.group_pattern = self._get_group_pattern(flags)
        
    def _get_group_pattern(self,flags):
        # combine phrases into a compound pattern
        patterns = []
        sub_pattern = sre_parse.Pattern()
        sub_pattern.flags = flags
        for phrase, action in self.lexicon:
            patterns.append(sre_parse.SubPattern(sub_pattern, [
                (SUBPATTERN, (len(patterns) + 1, sre_parse.parse(phrase, flags))),
                ]))
        sub_pattern.groups = len(patterns) + 1
        group_pattern = sre_parse.SubPattern(sub_pattern, [(BRANCH, (None, patterns))])
        return sre_compile.compile(group_pattern)

    def get_multiline(self,f,m):
        content = []
        next_line = ''
        while not re.search("^}",next_line):
            content.append(next_line)
            try:
                next_line = next(f)
            except StopIteration:
                # This will happen at end of file
                next_line = None
                break
        content = "".join(content)
        return content, next_line

    def get_item(self,f,line):
        # IMPORTANT: Each item needs to be added sequentially 
        # to make sure the record data is grouped properly
        # so make sure you add content by calling callback()
        # before doing any recursive calls
        match = self.group_pattern.scanner(line).match()
        if not match:
            return
        callback = self.lexicon[match.lastindex-1][1]
        if "def" in match.group():
            # this is a multi-line get
            first_line = match.group()
            body, current_line = self.get_multiline(f,match)
            sections = [first_line, body, current_line]
            content = "\n".join(sections).strip()
            callback(self,content)
            if current_line:
                self.get_item(f,current_line)
        else:
            callback(self,match.group(1))

    def scan(self,file_path):
        fin = open(file_path, 'r')
        for line in fin:
            self.get_item(fin,line)

    
class Parser(object):

    def __init__(self, groovy_file):
        self.methods = OrderedDict()
        # handler format: (pattern, callback)
        handlers = [ ("^def( .*)", self.add_method), ]
        Scanner(handlers).scan(groovy_file)

    def get_methods(self):
        return self.methods

    # Scanner Callback
    def add_method(self,scanner,token):
        method_definition = token
        method_signature = self._get_method_signature(method_definition)
        method_name = self._get_method_name(method_signature)
        method_body = self._get_method_body(method_definition)
        # NOTE: Not using sha1, signature, or the full method right now
        # because of the way the GSE works. It's easier to handle version
        # control by just using the method_body, which the GSE compiles,
        # creates a class out of, and stores in a classMap for reuse.
        # You can't do imports inside Groovy methods so just using the func body 
        sha1 = self._get_sha1(method_definition)
        #self.methods[method_name] = (method_signature, method_definition, sha1)
        method = Method(method_definition, method_signature, method_body, sha1)
        self.methods[method_name] = method

    def _get_method_signature(self,method_definition):
        pattern = '^def(.*){'
        return re.search(pattern,method_definition).group(1).strip()
            
    def _get_method_name(self,method_signature):
        pattern = '^(.*)\('
        return re.search(pattern,method_signature).group(1).strip()

    def _get_method_body(self,method_definition):
        # remove the first and last lines, and return just the method body
        lines = method_definition.split('\n')
        body_lines = lines[+1:-1]
        method_body = "\n".join(body_lines).strip()
        return method_body

    def _get_sha1(self,method_definition):
        # this is used to detect version changes
        sha1 = hashlib.sha1()
        sha1.update(method_definition)
        return sha1.hexdigest()




#print Parser("gremlin.groovy").get_methods()