module Rtml::Instruction::FollowClassMethods # Simply follows the supplied array of instructions. Each instruction is considered to have run on the result # of the previous instruction. If that result doesn't respond_to? the instruction or does not support # subprocessing, then the instruction is run on its parent result instead. # # Use "pop" as an instruction at any time to force the next instruction to run on the current result's parent. # # Examples: # # follow_instructions ['screen :init, :idle', # 'screen :idle', # 'display', # 'screen :main', # 'submit "http://www.google.com"', # 'getvar "txn.amount"' # ] # # The above instructions are equivalent to the following RTML code: # # screen :init, :idle # # screen :idle do # display # end # # screen :main do # submit "http://www.google.com" do # getvar "txn.amount" # end # end # def follow(instructions_array, options = {}) instructions = instructions_array options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) } if controller = options.delete(:controller) action = options.delete(:action) || "index" controller = "#{controller.camelize}Controller".constantize Rtml::actions_for controller do define_method action do Rtml::Instruction.follow instructions end end else rtml_document = options.delete(:rtml_document) || options.delete(:document) || Rtml::Document.new(:name => "untitled") instructions = instructions.raw.flatten if defined?(Cucumber) && instructions.kind_of?(Cucumber::Ast::Table) reset_stack(rtml_document) process_instructions(instructions) rtml_document end end def process_instructions(instructions) remaining = instructions if current = remaining.shift arguments = current.split method_name = arguments.shift arguments = arguments.join(" ").strip if current.strip == 'pop' @stack.pop elsif current_target.respond_to?(method_name) || @stack.length == 1 block = remaining.empty? ? nil : proc { |target| @stack.push target } evaluate_instruction(method_name, arguments, &block) else @stack.pop remaining = [current]+remaining end process_instructions(remaining) end end def evaluate_instruction(method_name, arguments, &block) arguments = "*[]" if arguments.blank? instruction = "current_target.#{method_name}(#{arguments}, &block)" eval(instruction, binding, __FILE__, __LINE__) rescue Rtml::Errors::SubprocessingNotSupported # This error is raised before the block is evaluated, hence, @stack.push never occurs, hence, there's # no need to pop. Simply perform the next instruction on the current_target, which hasn't changed. rescue SyntaxError # This is usually because instruction looked something like: # current_target.controller.response.status(= 500, &block) # The solution is to simply evaluate it exactly as it was written in the instruction: # [current_target.]controller.response.status = 500 eval("current_target.#{method_name} #{arguments}", binding, __FILE__, __LINE__) end def current_target @stack.last end def reset_stack(document) if @stack then @stack.clear else @stack = [] end @stack << document end end