lib/qed/script.rb in qed-1.3 vs lib/qed/script.rb in qed-2.0.0

- old
+ new

@@ -1,449 +1,116 @@ module QED require 'yaml' + require 'tilt' + require 'nokogiri' + require 'facets/dir/ascend' - require 'ae' + require 'qed/evaluator' - require 'qed/reporter/dotprogress' - require 'qed/reporter/summary' - require 'qed/reporter/verbatim' - #Assertion = AE::Assertion - Expectation = Assertor +# Expectation = Assertor - # Global Before - def self.Before(&procedure) - @_before = procedure if procedure - @_before - end - - # Global After - def self.After(&procedure) - @_after = procedure if procedure - @_after - end - - # New Specification - #def initialize(specs, output=nil) - # @specs = [specs].flatten - #end - # = Script # + # When run current working directory is changed to that of + # the demonstration script's. So any relative file references + # within a demo must take that into account. + # class Script - #def self.load(file, output=nil) - # new(File.read(file), output) - #end - - # Path of demonstration script. + # Demonstration file. attr :file - # Reporter object to issue output calls. - attr :output + # Expanded dirname of +file+. + attr :dir - # List of helper scripts to require. - attr :helpers + # + attr :scope # New Script - def initialize(file, output=nil) - @file = file - @output = output || Reporter::Verbatim.new #(self) - parse_document(file) + def initialize(file, scope=nil) + @file = file + @scope = scope || Scope.new + apply_environment end + # + def dir + @dir ||= File.expand_path(File.dirname(file)) + end + # File basename less extension. def name @name ||= File.basename(file).chomp(File.extname(file)) end - # - def directory - @directory ||= Dir.pwd #File.dirname(File.expand_path(file)) + # Nokogiri HTML document. + def document + @document ||= Nokogiri::HTML(to_html) end - #def convert - # @source.gsub(/^\w/, '# \1') - #end - - # Run the script. - def run - @lineno = 0 - - $LOAD_PATH.unshift(directory) - - import_helpers - - steps.each do |step| - output.report_step(step) - case step - when /^[=#]/ - output.report_header(step) - when /^\S/ - output.report_comment(step) - context.When.each do |(regex, proc)| - if md = regex.match(step) - proc.call(*md[1..-1]) - end - end - else - #if context.table - # run_table(step) - #else - run_step(step) - #end - end - @lineno += step.count("\n") - end - - $LOAD_PATH.index(directory){ |i| $LOAD_PATH.delete_at(i) } + # Root node of the html document. + def root + document.root end - #-- - # NOTE: The Around code is in place should we decide - # to use it. I'm not sure yet if it's really neccessary, - # since we have Before and After. - #++ - def run_step(step=nil, &blk) - QED.Before.call if QED.Before - context.Before.call if context.Before - begin - if blk # TODO: Is this still used? - blk.call #eval(step, context._binding) - else - #if context.Around - # context.Around.call do - # eval(step, context._binding, @file, @lineno+1) - # end - #else - eval(step, context._binding, @file, @lineno+1) - #end - end - output.report_pass(step) if step - rescue Assertion => error - output.report_fail(step, error) - rescue Exception => error - output.report_error(step, error) - ensure - context.After.call if context.After - QED.After.call if QED.After + # Open file and translate template into HTML text. + def to_html + #case file + #when /^http/ + # ext = File.extname(file).sub('.','') + # Tilt[ext].new{ source } + #else + #end + if File.extname(file) == '.html' + File.read(file) + else + Tilt.new(file).render end end -=begin - # - def run_table(step) - file = context.table - Dir.ascend(Dir.pwd) do |path| - f1 = File.join(path, file) - f2 = File.join(path, 'fixtures', file) - fr = File.file?(f1) ? f1 : File.exist?(f2) ? f2 : nil - (file = fr; break) if fr - end - output.report_pass(step) #step) - - tbl = YAML.load(File.new(file)) - key = tbl.shift - tbl.each do |set| - assign = key.zip(set).map{ |k, v| "#{k}=#{v.inspect};" }.join - run_table_step(assign + step, set) - #run_step(set.inspect.tabto(4)){ blk.call(set) } - #@_script.run_step(set.to_yaml.tabto(2)){ blk.call(set) } - #@_script.output.report_table(set) - end - #output.report_pass(step) #step) - context.table = nil + # Open, convert to HTML and cache. + def html + @html ||= to_html end # - #def run_table_step(step, set) - def run_table_step(set, &blk) - context.before.call if context.before - begin - #eval(step, context._binding, @file) # TODO: would be nice to know file and lineno here - blk.call(*set) - output.report_pass(' ' + set.inspect) #step) - rescue Assertion => error - output.report_fail(set.inspect, error) - rescue Exception => error - output.report_error(set.inspect, error) - ensure - context.after.call if context.after - end - end -=end - - # Cut-up script into steps. - def steps - @steps ||= ( - code = false - str = '' - steps = [] - @source.each_line do |line| - if /^\s*$/.match line - str << line - elsif /^[=]/.match line - steps << str #.chomp("\n") - steps << line #.chomp("\n") - str = '' - #str << line - code = false - elsif /^\S/.match line - if code - steps << str #.chomp("\n") - str = '' - str << line - code = false - else - str << line - end - else - if code - str << line - else - steps << str - str = '' - str << line - code = true - end - end - end - steps << str - #steps.map{ |s| s.chomp("\n") } - steps - ) - end - - # The run context. - def context - @context ||= Context.new(self) - end - - # - def import(helper) - code = File.read(helper) - eval(code, context._binding) - end - - private - - # Splits the document into main source and footer - # and extract the helper document references from - # the footer. - # - def parse_document(file) - text = File.read(file) - index = text.rindex('---') || text.size - source = text[0...index] - footer = text[index+3..-1].to_s.strip - helpers = parse_helpers(footer) - @source = source - @helpers = helpers - end - - # - def parse_helpers(footer) - helpers = [] - footer.split("\n").each do |line| - next if line.strip == '' - case line - when /\[(.*?)\]\((.*?)\)/ - helpers << $2 - when /(.*?)\[(.*?)\]/ - helpers << $2 - end - end - helpers - end - -=begin - # Looks for a master +helper.rb+ file and a special - # helpers/<name>.rb file. Both of these, when found, will - # be imported when this script is run. - - def collect_helpers - dir = File.dirname(file) - list = [] - list << "helper.rb" if File.exist?(File.join(dir, "helper.rb")) - list << "helpers/#{name}.rb" if File.exist?(File.join(dir, "helpers/#{name}.rb")) - list - end -=end - - # TODO: How to determine where to find the env.rb file? - #def require_environment - # dir = File.dirname(file) - # dir = File.expand_path(dir) - # env = loop do - # file = File.join(dir, 'env.rb') - # break file if File.exist?(file) - # break nil if ['demo', 'demos', 'doc', 'docs', 'test', 'tests'].include? File.basename(dir) - # break nil if dir == Dir.pwd - # dir = File.dirname(dir) - # end - # require(env) if env + #def source + # @source ||= ( + # #case file + # #when /^http/ + # # ext = File.extname(file).sub('.','') + # # open(file) + # #else + # File.read(file) + # #end + # ) #end - # FIXME: where to stop looking for helpers. - def import_helpers - hlp = [] - dir = Dir.pwd #File.expand_path(dir) - env = loop do - helpers.each do |helper| - file = File.join(dir, 'helpers', helper) - if File.exist?(file) - hlp << file - end - end - break if ['qed', 'demo', 'demos', 'doc', 'docs', 'test', 'tests'].include? File.basename(dir) - dir = File.dirname(dir) - end - hlp.each{ |helper| import(helper) } - end - - end - - # - class Context < Module - - TABLE = /^TABLE\[(.*?)\]/i - - def initialize(script) - @_script = script - @_when = [] - @_tables = [] - end - - def _binding - @_binding ||= binding - end - - # Before each step. - def Before(&procedure) - @_before = procedure if procedure - @_before - end - - # After each step. - def After(&procedure) - @_after = procedure if procedure - @_after - end - - # Run code around each step. # - # Around procedures must take a block, in which the step is run. - # - # Around do |&step| - # ... do something here ... - # step.call - # ... do stiff stuff ... - # end - # - #def Around(&procedure) - # @_around = procedure if procedure - # @_around - #end - - # Comment match procedure. - # - # This is useful for creating unobtrusive setup and (albeit more - # limited) teardown code. A pattern is matched against each comment - # as it is processed. If there is match, the code procedure is - # triggered, passing in any mathcing expression arguments. - # - def When(pattern=nil, &procedure) - return @_when unless procedure - raise ArgumentError unless pattern - unless Regexp === pattern - pattern = __when_string_to_regexp(pattern) - end - @_when << [pattern, procedure] + def run(*observers) + evaluator = Evaluator.new(self, *observers) + evaluator.run end - # Code match-and-transform procedure. # - # This is useful to transform human readable code examples - # into proper exectuable code. For example, say you want to - # run shell code, but want to make if look like typical - # shelle examples: - # - # $ cp fixture/a.rb fixture/b.rb - # - # You can use a transform to convert lines starting with '$' - # into executable Ruby using #system. - # - # system('cp fixture/a.rb fixture/b.rb') - # - #def Transform(pattern=nil, &procedure) - # - #end - - # Table-based steps. - def Table(file=nil, &blk) - file = file || @_tables.last - tbl = YAML.load(File.new(file)) - tbl.each do |set| - blk.call(*set) - end - @_tables << file + def environment + glob = File.join(dir, '{environment,common,shared}', '*') + Dir[glob] end - # Read/Write a fixture. - def Data(file, &content) - raise if File.directory?(file) - if content - FileUtils.mkdir_p(File.dirname(fname)) + # + def apply_environment + environment.each do |file| case File.extname(file) - when '.yml', '.yaml' - File.open(file, 'w'){ |f| f << content.call.to_yaml } + when '.rb' + eval(File.read(file), scope.__binding__, file) else - File.open(file, 'w'){ |f| f << content.call } + Script.new(file, scope).run end - else - #raise LoadError, "no such fixture file -- #{fname}" unless File.exist?(fname) - case File.extname(file) - when '.yml', '.yaml' - YAML.load(File.new(file)) - else - File.read(file) - end end end - - private - - def __when_string_to_regexp(str) - str = str.split(/(\(\(.*?\)\))(?!\))/).map{ |x| - x =~ /\A\(\((.*)\)\)\z/ ? $1 : Regexp.escape(x) - }.join - str = str.gsub(/(\\\ )+/, '\s+') - Regexp.new(str, Regexp::IGNORECASE) - - #rexps = [] - #str = str.gsub(/\(\((.*?)\)\)/) do |m| - # rexps << '(' + $1 + ')' - # "\0" - #end - #str = Regexp.escape(str) - #rexps.each do |r| - # str = str.sub("\0", r) - #end - #str = str.gsub(/(\\\ )+/, '\s+') - #Regexp.new(str, Regexp::IGNORECASE) - end - - # - # check only local and maybe start paths - #def __locate_file(file) - # Dir.ascend(Dir.pwd) do |path| - # f1 = File.join(path, file) - # f2 = File.join(path, 'fixtures', file) - # fr = File.file?(f1) ? f1 : File.exist?(f2) ? f2 : nil - # (file = fr; break) if fr - # end - #end end end