add support for non-expression, i.e. full program compositions
also stop renaming function definitions to `func`
Fixes #5
diff --git a/action/__main__.py b/action/__main__.py
index a75de50..ec9839e 100755
--- a/action/__main__.py
+++ b/action/__main__.py
@@ -19,6 +19,9 @@
from composer import deserialize
+class My:
+ composition = None
+
#
# this is the main method for running the pycomposer as an OpenWhisk action
#
@@ -28,11 +31,44 @@
if 'composition' in args:
print('accepting composition as input')
- print(args['composition'])
+# print(args['composition'])
composition = deserialize(args['composition'])
else:
- print(args['source'])
- composition = eval(args['source'])
+ print('accepting source as input')
+# print(args['source'])
+# composition = eval(args['source'])
+ code = args['source']
+
+ try:
+ print('trying eval')
+ composition = eval(code)
+ print('eval worked!')
+
+ except SyntaxError as error:
+ # if the code isn't an expression, eval will fail with a syntax error;
+ # admittedly the eval might've failed for a more "true" syntax error, but
+ # the best we can do is hope for the best, and resort to an exec
+ print('eval did not work; falling back to exec')
+
+ name = args['name'] if 'name' in args else 'action'
+ path = f'/tmp/{name}'
+ file = open(path, 'w')
+ file.write(code)
+ file.close()
+
+ file = open(path, 'r')
+
+ try:
+ x = compile(file.read(), path, 'exec')
+ my = My()
+ exec(x, {'my': my, 'composer': composer}) # we use `my` as an outval
+ composition = my.composition
+
+ finally:
+ file.close()
+
+ if composition is None:
+ raise Exception('Source did not produce a composition')
if 'lower' in args:
res = composer.lower(composition, args['lower'])
@@ -50,8 +86,6 @@
comp['composition'] = json.loads(str(composer.lower(comp['composition'], compat)))
print('success in encode')
- print(comp)
- print(str(comp))
return comp
# return { "code": composer.encode(composer.composition(name, composition), args['encode'])['actions'][-1]['action']['exec']['code'] }
diff --git a/src/composer/composer.py b/src/composer/composer.py
index 324694b..57ec5e1 100644
--- a/src/composer/composer.py
+++ b/src/composer/composer.py
@@ -201,8 +201,11 @@
if isinstance(exc, str):
if exc.startswith('def'):
# standardize function name
- exc = re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(', 'def func(', exc)
- exc = { 'kind': 'python:3', 'code': exc }
+ pattern = re.compile('def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(')
+ match = pattern.match(exc)
+ functionName = match.group(1)
+# exc = pattern.sub('def func(', exc)
+ exc = { 'kind': 'python:3', 'code': exc, 'functionName': functionName }
else: # lambda
exc = { 'kind': 'python:3+lambda', 'code': exc }
diff --git a/src/composer/conductor.py b/src/composer/conductor.py
index c9fd247..139212d 100644
--- a/src/composer/conductor.py
+++ b/src/composer/conductor.py
@@ -159,7 +159,7 @@
inspect_errors() # handle error objects when resuming
# run function f on current stack
- def run(f, kind):
+ def run(f, kind, functionName=None):
# handle let/mask pairs
view = []
n = 0
@@ -194,7 +194,7 @@
# collapse stack for invocation
env = reduceRight(lambda acc, cur: update(acc, cur['let']) if 'let' in cur and isinstance(cur['let'], dict) else acc, {}, view)
if kind == 'python:3':
- main = '''exec(code + "\\n__out__['value'] = func(env, args)", {'env': env, 'args': args, '__out__':__out__})'''
+ main = '''exec(code + "\\n__out__['value'] = ''' + functionName + '''(env, args)", {'env': env, 'args': args, '__out__':__out__})'''
code = f
else: # lambda
main = '''__out__['value'] = code(env, args)'''
@@ -239,7 +239,8 @@
elif jsonv['type'] == 'function':
result = None
try:
- result = run(jsonv['exec']['code'], jsonv['exec']['kind'])
+ functionName = jsonv['exec']['functionName'] if 'functionName' in jsonv['exec'] else None
+ result = run(jsonv['exec']['code'], jsonv['exec']['kind'], functionName)
except Exception as error:
print(error)
result = { 'error': 'An exception was caught at state '+str(current)+' (see log for details)' }