module QED #require 'ae' # Scope is the context in which QED documents are run. # class Scope < Module # Location of `qed/scope.rb`. DIRECTORY = File.dirname(__FILE__) # Setup new Scope instance. def initialize(demo, options={}) super() @_applique = demo.applique_prime @_file = demo.file @_root = options[:root] || $ROOT # FIXME @_features = [] include *demo.applique # TODO: custom extends? __create_clean_binding_method__ end # This turns out to be the key to proper scoping. def __create_clean_binding_method__ module_eval %{ def __binding__ @__binding__ ||= binding end } end # def include(*modules) super(*modules) extend self # overcome dynamic inclusion problem end # Expanded dirname of +file+. def demo_directory @_demo_directory ||= File.expand_path(File.dirname(@_file)) end # Evaluate code in the context of the scope's special binding. # The return value of the evaluation is stored in `@_`. # def evaluate(code, file=nil, line=nil) if file @_ = eval(code, __binding__, file.to_s, line.to_i) else @_ = eval(code, __binding__) end end # TODO: Alternative to Plugin gem? If not improve and make standard requirement. # Utilize is like #require, but will evaluate the script in the context # of the current scope. # def utilize(file) file = Dir[DIRECTORY + "/helpers/#{file}"].first if !file require 'plugin' file = Plugin.find("#{file}{,.rb}", :directory=>nil) end if file && !@_features.include?(file) code = File.read(file) evaluate(code, nil, file) else raise LoadError, "no such file -- #{file}" end end # Define "when" advice. def When(*patterns, &procedure) #patterns = patterns.map{ |pat| pat == :text ? :desc : pat } @_applique.When(*patterns, &procedure) end # Define "before" advice. Default type is :each, which # evaluates just before example code is run. def Before(type=:each, &procedure) type = :step if type == :each type = :demo if type == :all @_applique.Before(type, &procedure) end # Define "after" advice. Default type is :each, which # evaluates just after example code is run. def After(type=:each, &procedure) type = :step if type == :each type = :demo if type == :all @_applique.After(type, &procedure) end # Directory of current document. def __DIR__(file=nil) if file Dir.glob(File.join(File.dirname(@_file), file)).first || file else File.dirname(@_file) end end # TODO: Should Table and Data be extensions that can be optionally loaded? # TODO: Cache Table and Data for speed ? # Use sample table to run steps. The table file is located relative to # the demo, failing that it will be looked for relative to the working # directory. # def Table(file=nil, options={}) #:yield: if file file = Dir.glob(File.join(File.dirname(@_file), file)).first || file else file = @_last_table end @_last_table = file file_handle = File.new(file) if options[:stream] if block_given? YAML.load_documents(file_handle) do |data| yield data end else YAML.load_stream(file_handle) end else if block_given? tbl = YAML.load(file_handle) tbl.each do |data| yield(*data) end else YAML.load(file_handle) end end end # Read a static data file and yield contents to block if given. # # This method no longer automatically uses YAML.load. def Data(file) #:yield: file = Dir.glob(File.join(File.dirname(@_file), file)).first || file #case File.extname(file) #when '.yml', '.yaml' # data = YAML.load(File.new(file)) #else data = File.read(file) #end if block_given? yield(data) else data end end # Helper method to clear temporary work directory. # def clear_working_directory! dir = @_root dir = File.expand_path(dir) if dir == '/' or dir == File.expand_path('~') abort "DANGER! Trying to use home or root as a temporary directory!" end entries = Dir.glob(File.join(dir, '**/*')) dirs, files = entries.partition{ |f| File.directory?(f) } files.each { |file| FileUtils.rm(file) } dirs.each { |dir| FileUtils.rmdir(dir) } end # TODO: Project's root directory. #def rootdir # @_root #end # Redirect constant missing to toplevel (i.e. Object). This is # to allow the evaluation scope to emulate the toplevel. def const_missing(const) Object.const_get(const) end end#class Scope end#module QED