def _while(states, scope, arguments, block): if not len(block.lines): raise InvalidArguments('A block is required in `while`') if len(arguments.args) != 1 \ or len(arguments.kwargs): raise InvalidArguments('`while` only accepts one conditional argument') var = arguments.args[0] retval = None while var.python_value(scope): if retval is None: retval = '' try: retval += str(block.python_value(scope)) except BreakException as e: retval += e.retval break except ContinueException as e: retval += e.retval continue else: if retval is None: return [Continue(), EmptyState(), ElseState()], '' return [Continue(), ElseState()], retval return [Continue()], retval
def define_block(states, scope, arguments, block): if not len(arguments.args): raise InvalidArguments('A block name is required in `block`') if len(arguments.args) != 1 or len(arguments.kwargs): raise InvalidArguments('`block` only accepts one argument') block_name = arguments.args[0].python_value(scope) scope['__blocks'][block_name] = block return states, ''
def include(states, scope, arguments, block): if len(arguments.args) != 1: raise InvalidArguments('`include` only accepts one argument') if len(block.lines): raise InvalidArguments('`include` does not accept a block') restore_scope = {} delete_scope = [] if isinstance(scope['self'], PlywoodValue): context = scope['self'].python_value(scope) else: context = scope['self'] if len(arguments.kwargs): kwargs = dict( (item.key.get_name(), item.value) for item in arguments.kwargs ) for key, value in kwargs.items(): if key in context: restore_scope[key] = context[key] else: delete_scope.append(key) context[key] = value if '__env' in scope: env = scope['__env'] else: env = PlywoodEnv({'separator': ' '}) template_name = arguments.args[0].python_value(scope) plywood = None for path in scope['__paths']: template_path = os.path.join(path, template_name) + '.ply' if template_path in env.includes: plywood = env.includes[template_path] elif os.path.exists(template_path): retval = '' with open(template_path) as f: input = f.read() plywood = Plywood(input) env.includes[template_path] = plywood # scope is not pushed/popped - `include` adds its variables to the local scope. if plywood: break if not plywood: raise Exception('Could not find template: {0!r}'.format(template_name)) retval = plywood.run(context, env) if len(arguments.kwargs): for key, value in restore_scope.items(): context[key] = value for key in delete_scope: del context[key] return states, retval
def _import(states, scope, arguments, block): if len(arguments.kwargs): raise InvalidArguments('`import` does not accept keyword arguments') if len(block.lines): raise InvalidArguments('`import` does not accept a block') for arg in arguments.args: scope_name, import_name = join_module_names(arg) scope[scope_name] = __import__(import_name) return states, ''
def _elif(states, scope, arguments, block): if not len(block.lines): raise InvalidArguments('A block is required in `elif`') if len(arguments.args) != 1: raise InvalidArguments( 'A condition (and only one condition) is required in `elif`') if len(arguments.args) != 1 or len(arguments.kwargs): raise InvalidArguments('`elif` only accepts one argument') arg = arguments.args[0].python_value(scope) if arg: return [Continue()], block.get_value(scope) return [Continue(), ElseState()], ''
def _for(states, scope, arguments, block): if not len(block.lines): raise InvalidArguments('A block is required in `for`') if len(arguments.args) != 1 \ or len(arguments.kwargs) \ or not isinstance(arguments.args[0], PlywoodOperator) \ or arguments.args[0].operator != 'in': raise InvalidArguments('`for` only accepts an `in` operation') var = arguments.args[0].left if not isinstance(var, PlywoodVariable) and not isinstance( var, PlywoodParens): raise InvalidArguments( '`for` expects a variable name or tuple of variable names') if isinstance(var, PlywoodParens): if var.kwargs: raise InvalidArguments( 'keyword arguments are not in appropriate in the list of `for` variable names' ) for arg in var.arguments: if not isinstance(arg, PlywoodVariable): raise InvalidArguments( '`for` expects a variable name or tuple of variable names') iterator = arguments.args[0].right.python_value(scope) retval = None if not iterator: return [Continue(), EmptyState(), ElseState()], '' for for_value in iterator: if retval is None: retval = '' if isinstance(var, PlywoodVariable): varname = var.get_name() scope[varname] = for_value elif isinstance(var, PlywoodParens): raise Exception('huh?') # for_values should be a PlywoodList, PlywoodDict, # if len(var.arguments) != # scope[] pass try: retval += str(block.python_value(scope)) except BreakException as e: retval += e.retval break except ContinueException as e: retval += e.retval continue else: if retval is None: return [Continue(), EmptyState(), ElseState()], '' return [Continue(), ElseState()], retval return [Continue()], retval
def _from(states, scope, arguments, block): if len(arguments.kwargs): raise InvalidArguments('`from` does not accept keyword arguments') if len(block.lines): raise InvalidArguments('`from` does not accept a block') if len(arguments.args) != 2: raise InvalidArguments( '`from` only accepts a module and a list of attributes to import') _, target = join_module_names(arguments.args[0]) imports = arguments.args[1] if len(imports.args) == 1 and isinstance(imports.args[0], PlywoodParens): imports = imports.args[0] module = __import__(target) for var in imports.args: attr = var.get_name() scope[attr] = getattr(module, attr) return states, ''
def extends(states, scope, arguments, block): if not len(arguments.args): raise InvalidArguments('A layout name is required in `extends`') if len(arguments.args) != 1: raise InvalidArguments('`extends` only accepts one argument') scope.push() yield_content = block.get_value( scope) # executes the body, to define blocks scope[ '__yield'] = yield_content # makes the extends body available to the yield method # inside the extended layout, block => get_block scope['block'] = scope['get_block'] # include the layout states, retval = include(states, scope, arguments, PlywoodBlock(block.location, [])) scope.pop() return states, retval
def join_module_names(token, scope_name=None): if isinstance(token, PlywoodOperator) and token.operator == '.': if scope_name is None: scope_name = token.left.get_name() return scope_name, token.left.get_name() + '.' + join_module_names( token.right, scope_name)[1] if not isinstance(token, PlywoodVariable): raise InvalidArguments( 'Only variable names are allowed in an import statement') if scope_name is None: scope_name = token.get_name() return scope_name, token.get_name()
def the_function(states, called_scope, called_arguments, called_block): args = called_arguments.args kwargs = called_arguments.kwargs local_arglist = [] + final_arglist called_scope.push() for plywood_kwarg in kwargs: local_value = plywood_kwarg.value.python_value(scope) local_var_name = plywood_kwarg.key.python_value(scope) if local_var_name in local_arglist: local_arglist.remove(local_var_name) else: raise InvalidArguments('Unknown keyword argument {0!r}'.format(local_var_name)) called_scope[local_var_name] = local_value for plywood_value in args: local_value = plywood_value.python_value(called_scope) if not local_arglist: raise InvalidArguments('Too many arguments passed to {0}'.format(function_name)) local_var_name = local_arglist.pop(0) called_scope[local_var_name] = local_value for local_var_name, local_value in defaults.items(): if local_var_name in local_arglist: local_arglist.remove(local_var_name) called_scope[local_var_name] = local_value if local_arglist: raise InvalidArguments('Unassigned values: {0}'.format(', '.join(local_arglist))) def return_function(arg): raise ReturnException(arg) called_scope['return'] = PlywoodFunction(return_function) try: retval = block.get_value(called_scope) except ReturnException as e: retval = e.retval called_scope.pop() return [Continue()], retval
def get_block(states, scope, arguments, block): if not len(arguments.args): raise InvalidArguments('A block name is required in `get_block`') if len(arguments.args) != 1 or len(arguments.kwargs): raise InvalidArguments('`get_block` only accepts one argument') # if a block is defined, it will be stored as the default block_name = arguments.args[0].python_value(scope) if block.lines: if block_name in scope['__blocks']: scope.push() scope['super'] = block.get_value(scope) scope['super'].suppress_nl = True retval = scope['__blocks'][block_name].get_value(scope) scope.pop() else: retval = block.get_value(scope) else: if block_name in scope['__blocks']: retval = scope['__blocks'][block_name].get_value(scope) else: retval = '' return states, retval
def _def(states, scope, arguments, block): ''' Example: def foo(bar, baz, quux='value') foo => name of function [bar, baz] => arguments, no default values. These must be variable names. Sorry, no support for *args and **kwargs just yet. ''' if not len(block.lines): raise InvalidArguments('A block is required in `def`') if len(arguments.args) != 1 \ or len(arguments.kwargs) \ or not isinstance(arguments.args[0], PlywoodCallOperator) \ or arguments.args[0].operator != '()': raise InvalidArguments('`def` should be followed by a function definition') function_name = arguments.args[0].left.get_name() arglist = arguments.args[0].right.args kwarglist = arguments.args[0].right.kwargs final_arglist = [] defaults = {} # all arglist.args should be a varname for var_name in arglist: if not isinstance(var_name, PlywoodVariable): raise InvalidArguments('`def` should be given a list of variable names. ' '{0!r} is invalid'.format(var_name.python_value(scope))) final_arglist.append(var_name.get_name()) for kwarg in kwarglist: var_name = kwarg.key.python_value(scope) final_arglist.append(var_name) value = kwarg.value.python_value(scope) defaults[var_name] = value # Now we have to re-produce python's way of accepting arguments, but I'm # gonna get away with being sloppy about it... # First, any kwargs can be assigned, and removed from the final_arglist. # Then, args is scanned and values are assigned to corresponding names in # final_arglist. If any values are left over in final_arglist, args, or # kwargs, something went wrong. # # The sloppy part comes because unlike python, you can specify args and # kwargs in pretty much any order. All kwargs are assigned first, then # the args are assigned to whatever is left in the local_arglist, then # defaults. Only after all that is local_arglist checked to make sure it is # empty. def the_function(states, called_scope, called_arguments, called_block): args = called_arguments.args kwargs = called_arguments.kwargs local_arglist = [] + final_arglist called_scope.push() for plywood_kwarg in kwargs: local_value = plywood_kwarg.value.python_value(scope) local_var_name = plywood_kwarg.key.python_value(scope) if local_var_name in local_arglist: local_arglist.remove(local_var_name) else: raise InvalidArguments('Unknown keyword argument {0!r}'.format(local_var_name)) called_scope[local_var_name] = local_value for plywood_value in args: local_value = plywood_value.python_value(called_scope) if not local_arglist: raise InvalidArguments('Too many arguments passed to {0}'.format(function_name)) local_var_name = local_arglist.pop(0) called_scope[local_var_name] = local_value for local_var_name, local_value in defaults.items(): if local_var_name in local_arglist: local_arglist.remove(local_var_name) called_scope[local_var_name] = local_value if local_arglist: raise InvalidArguments('Unassigned values: {0}'.format(', '.join(local_arglist))) def return_function(arg): raise ReturnException(arg) called_scope['return'] = PlywoodFunction(return_function) try: retval = block.get_value(called_scope) except ReturnException as e: retval = e.retval called_scope.pop() return [Continue()], retval the_function.__name__ = function_name scope[function_name] = PlywoodRuntime(the_function) return [Continue()], ''
def _yield(states, scope, arguments, block): if len(arguments.args) or len(arguments.kwargs) or len(block.lines): raise InvalidArguments( '`yield` does not accept any arguments or block') yield_content = scope.get('__yield', '') return states, yield_content
def _else(states, scope, arguments, block): if not len(block.lines): raise InvalidArguments('A block is required in `else`') if len(arguments.args) or len(arguments.kwargs): raise InvalidArguments('`else` does not accept any arguments') return [Continue()], block.get_value(scope)
def _open(states, scope, arguments, block): if len(block.lines): raise InvalidArguments('`import` does not accept a block') args = map(lambda arg: arg.python_value(scope), arguments.args) retval = open(*args) return states, retval