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()