Source code for goblin.gremlin.groovy
import collections
import pyparsing
import re
# Cache of parsed files
_parsed_file_cache = {}
GroovyImport = collections.namedtuple('GroovyImport', ['comment_list', 'import_strings', 'import_list'])
GroovyFunction = collections.namedtuple('GroovyFunction', ['name', 'args', 'body', 'defn'])
GroovyFileDef = collections.namedtuple('GroovyFileDefinition', ['functions', 'imports', 'filename'])
[docs]class GroovyFunctionParser(object):
"""
Given a string containing a single function definition this class will parse the function definition and return
information regarding it.
"""
# Simple Groovy sub-grammar definitions
KeywordDef = pyparsing.Keyword('def')
VarName = pyparsing.Regex(r'[A-Za-z_]\w*')
FuncName = VarName
FuncDefn = KeywordDef + FuncName + "(" + pyparsing.delimitedList(VarName) + ")" + "{"
@classmethod
[docs] def parse(cls, data):
"""
Parse the given function definition and return information regarding the contained definition.
:param data: The function definition in a string
:type data: str | basestring
:rtype: dict
"""
try:
# Parse the function here
result = cls.FuncDefn.parseString(data)
result_list = result.asList()
args = result_list[3:result_list.index(')')]
# Return single line or multi-line function body
fn_body = re.sub(r'[^\{]+\{', '', data, count=1)
parts = fn_body.strip().split('\n')
fn_body = '\n'.join(parts[0:-1])
return GroovyFunction(result[1], args, fn_body, data)
except Exception as ex:
return None
[docs]class GroovyImportParser(object):
"""
Given a string containing a single import definition this class will parse the import definition and return
information regarding it.
"""
# Simple Groovy sub-grammar definitions
ImportDef = pyparsing.Suppress(pyparsing.Keyword('import'))
ImportVarName = pyparsing.Regex(r'[A-Za-z_.\*]*')
CommentVar = pyparsing.Word(pyparsing.alphas, pyparsing.alphanums).setName('comment')
OptionalSpace = pyparsing.Optional(' ')
ImportDefn = ImportDef + \
pyparsing.delimitedList(ImportVarName, delim='.').setResultsName('imports') + \
pyparsing.Suppress(";") + \
pyparsing.Optional(
pyparsing.Suppress('//') +
pyparsing.delimitedList(CommentVar, delim=pyparsing.Empty()).setResultsName('comment')
)
@classmethod
[docs] def parse(cls, data):
"""
Parse the given import and return information regarding the contained import statement.
:param data: The import statement in a string
:type data: str | basestring
:rtype: dict
"""
try:
# Parse the function here
result = cls.ImportDefn.parseString(data)
package_list = []
if 'imports' in result:
package_list = result['imports'].asList()
comment_list = []
if 'comment' in result:
comment_list = result['comment'].asList()
return GroovyImport(comment_list,
package_list,
['import {};'.format(package) for package in package_list])
except Exception as ex:
return None
[docs]def parse(filename):
"""
Parse Groovy code in the given file and return a list of information about each function necessary for usage in
queries to database.
:param filename: The file containing groovy code.
:type filename: str
:rtype: list
"""
# Check cache before parsing file
global _parsed_file_cache
if filename in _parsed_file_cache:
return _parsed_file_cache[filename]
ImportDefnRegexp = r'^import.*'
FuncDefnRegexp = r'^def.*\{'
FuncEndRegexp = r'^\}.*$'
passedFirstFunction = False
with open(filename, 'r') as f:
file_lines = [line.rstrip('\n') for line in f.readlines()]
all_fns = []
all_imports = []
fn_lines = ''
for line in file_lines:
if not passedFirstFunction and re.match(ImportDefnRegexp, line):
all_imports.append(line)
elif len(fn_lines) > 0:
if re.match(FuncEndRegexp, line):
fn_lines += line + "\n"
all_fns.append(fn_lines)
fn_lines = ''
else:
fn_lines += line + "\n"
elif re.match(FuncDefnRegexp, line):
fn_lines += line + "\n"
passedFirstFunction = True
import_results = [GroovyImportParser.parse(im) for im in all_imports]
func_results = [GroovyFunctionParser.parse(fn) for fn in all_fns]
result = GroovyFileDef(func_results, import_results, filename)
_parsed_file_cache[filename] = result
return result