├── lib ├── qed.yml ├── qed │ ├── helpers │ │ ├── shell_session.rb │ │ └── file_fixtures.rb │ ├── cli.rb │ ├── configure.rb │ ├── qparser.rb │ ├── document │ │ ├── markup.rb │ │ └── template.rhtml │ ├── cli │ │ ├── qedoc.rb │ │ └── qed.rb │ ├── reporter │ │ ├── dotprogress.rb │ │ ├── linear.rb │ │ ├── verbatim.rb │ │ ├── html.rb │ │ ├── tapy.rb │ │ └── abstract.rb │ ├── core_ext.rb │ ├── utils.rb │ ├── demo.rb │ ├── parser.rb │ ├── applique.rb │ ├── settings.rb │ ├── scope.rb │ ├── step.rb │ ├── session.rb │ ├── document.rb │ └── evaluator.rb └── qed.rb ├── demo ├── applique │ ├── ae.rb │ ├── fileutils.rb │ ├── constant.rb │ ├── env.rb │ ├── quote.md │ ├── toplevel.rb │ └── markup.rb ├── samples │ ├── table.yml │ └── data.txt ├── helpers │ ├── toplevel.rb │ └── advice.rb ├── 99_issues │ └── 02_topcode.md ├── 10_constant_lookup.md ├── 08_cross_script.md ├── 09_cross_script.md ├── 05_quote.md ├── 07_toplevel.md ├── 11_embedded_rules.md ├── 01_demos.md ├── 03_helpers.md ├── 04_samples.md └── 02_advice.md ├── Gemfile ├── bin ├── qed └── qedoc ├── .gitignore ├── .yardopts ├── work ├── consider │ ├── break │ │ ├── ruby-break │ │ └── lib │ │ │ └── break │ │ │ └── break.rb │ ├── scope_study.rb │ └── extract.rb ├── samples │ ├── website.rdoc │ ├── hello_world.rdoc │ ├── view_error.rdoc │ ├── messy.rdoc │ ├── rule_step.rdoc │ └── bench.rdoc ├── experiment │ ├── assertor.rb │ ├── section_slicer.rb │ ├── scope.rb │ └── run.rb ├── trash │ ├── assertion.rb │ ├── grammar │ │ ├── should.rb │ │ ├── assert.rb │ │ ├── expect.rb │ │ └── legacy │ │ │ └── assert.rb │ └── expectation.rb ├── defunct │ ├── yard-qed.rb │ ├── config.rb │ ├── doubles │ │ ├── stub.rb │ │ ├── mock.rb │ │ └── spy.rb │ └── advice.rb └── Notes.rdoc ├── .travis.yml ├── .ergo └── script.rb ├── Rakefile ├── etc └── qed.rb ├── Assembly ├── Indexfile ├── .index ├── LICENSE.txt ├── MANIFEST ├── .gemspec └── README.md /lib/qed.yml: -------------------------------------------------------------------------------- 1 | ../.index -------------------------------------------------------------------------------- /demo/applique/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | -------------------------------------------------------------------------------- /demo/applique/fileutils.rb: -------------------------------------------------------------------------------- 1 | require 'tmpdir' 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /demo/applique/constant.rb: -------------------------------------------------------------------------------- 1 | APPLIQUE_CONSTANT = true 2 | 3 | -------------------------------------------------------------------------------- /bin/qed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'qed/cli' 3 | QED.cli(*ARGV) 4 | 5 | -------------------------------------------------------------------------------- /demo/applique/env.rb: -------------------------------------------------------------------------------- 1 | Before do 2 | @steps ||= 0 3 | @steps += 1 4 | end 5 | 6 | -------------------------------------------------------------------------------- /bin/qedoc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'qed/cli' 3 | QED::Document.cli(*ARGV) 4 | 5 | -------------------------------------------------------------------------------- /demo/samples/table.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - [ try , TRY ] 3 | - [ me , ME ] 4 | - [ mc , MC ] 5 | 6 | -------------------------------------------------------------------------------- /demo/helpers/toplevel.rb: -------------------------------------------------------------------------------- 1 | class ToplevelExample 2 | def foo 3 | "foo" 4 | end 5 | end 6 | 7 | -------------------------------------------------------------------------------- /lib/qed/helpers/shell_session.rb: -------------------------------------------------------------------------------- 1 | # This extension provides a basic means for testing 2 | # shell commands. 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ergo/digest/ 2 | .yardoc/ 3 | doc/ 4 | log/ 5 | pkg/ 6 | tmp/ 7 | web/ 8 | work/sandbox/ 9 | *.lock 10 | *.gem 11 | 12 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --plugin qed 2 | --output-dir doc 3 | --readme README.md 4 | --title "Q.E.D." 5 | --protected 6 | --private 7 | lib 8 | - 9 | *.md 10 | *.txt 11 | -------------------------------------------------------------------------------- /work/consider/break/ruby-break: -------------------------------------------------------------------------------- 1 | #! /usr/bin/ruby1.8 2 | 3 | require 'quarry/breakout' 4 | 5 | begin 6 | load $file = ARGV[0] 7 | rescue Exception => error 8 | Quarry::Breakout.new(error).edit 9 | end 10 | 11 | -------------------------------------------------------------------------------- /demo/applique/quote.md: -------------------------------------------------------------------------------- 1 | ## Applique: Quote 2 | 3 | This applique simply provide support to `05_quote.md` demo. 4 | 5 | When "we want to make an example out of the following text" do |text| 6 | @quote_text = text 7 | end 8 | 9 | -------------------------------------------------------------------------------- /lib/qed/cli.rb: -------------------------------------------------------------------------------- 1 | if RUBY_VERSION < '1.9' 2 | require 'qed/configure' 3 | require 'qed/cli/qed' 4 | require 'qed/cli/qedoc' 5 | else 6 | require_relative 'configure' 7 | require_relative 'cli/qed' 8 | require_relative 'cli/qedoc' 9 | end 10 | 11 | -------------------------------------------------------------------------------- /demo/99_issues/02_topcode.md: -------------------------------------------------------------------------------- 1 | x = "Is this running?" 2 | x.assert == "Is this running?" 3 | 4 | This demo simply checks to make sure top code is exectued 5 | like any other code when there is no prior description. 6 | 7 | x.assert == "Is this running?" 8 | 9 | -------------------------------------------------------------------------------- /work/samples/website.rdoc: -------------------------------------------------------------------------------- 1 | = Addition 2 | 3 | require 'calculator' 4 | calculator = Caclulater.new 5 | 6 | A Calculator can add two numbers. 7 | 8 | calculator.push 2 9 | calculator.push 2 10 | calculator.add 11 | calculator.output.assert == 4 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec qed" 4 | rvm: 5 | - 2.1.0 6 | - 2.0.0 7 | - 1.9.3 8 | - 1.9.2 9 | - 1.8.7 10 | - rbx 11 | - jruby 12 | - ree 13 | matrix: 14 | allow_failures: 15 | - rvm: rbx 16 | cache: bundler 17 | 18 | -------------------------------------------------------------------------------- /work/experiment/assertor.rb: -------------------------------------------------------------------------------- 1 | module AE 2 | 3 | def self.hello? 4 | "here" 5 | end 6 | 7 | class Assertor < BasicObject 8 | 9 | def compare_message 10 | AE.hello? 11 | end 12 | 13 | end 14 | 15 | end 16 | 17 | def assert 18 | AE::Assertor.new.compare_message 19 | end 20 | -------------------------------------------------------------------------------- /work/experiment/section_slicer.rb: -------------------------------------------------------------------------------- 1 | s = <<-END 2 | This is an example. 3 | a = 1 4 | Of what I mean. 5 | b = 2 6 | And it can go on 7 | like this. 8 | 9 | c = 3 10 | For ever and ever. 11 | 12 | d = 4 13 | END 14 | 15 | a = s.scan(/(.*?(\s+)\s+[^\n]+?\n(?=\2\S|\z))/m) 16 | 17 | p a 18 | #p a.map{ |x| x[0] } 19 | -------------------------------------------------------------------------------- /demo/applique/toplevel.rb: -------------------------------------------------------------------------------- 1 | 2 | # Define a toplevel class to ensure it is accessible. 3 | class ToplevelClass 4 | end 5 | 6 | # Define a toplevel method to ensure it is also accessible. 7 | def toplevel_method 8 | true 9 | end 10 | 11 | # Define a toplevel method to ensure it is also accessible. 12 | def self.toplevel_singleton_method 13 | true 14 | end 15 | 16 | -------------------------------------------------------------------------------- /demo/applique/markup.rb: -------------------------------------------------------------------------------- 1 | When "lets say we have", "document called (((.*?))) with the following contents" do |file, text| 2 | file = Dir.tmpdir + '/sow/examples/' + file 3 | FileUtils.mkdir_p(File.dirname(file)) 4 | File.open(file, 'w'){ |f| f << text } 5 | end 6 | 7 | When 'when we run these examples' do 8 | Dir.chdir(Dir.tmpdir + '/sow/examples/') 9 | end 10 | 11 | -------------------------------------------------------------------------------- /work/samples/hello_world.rdoc: -------------------------------------------------------------------------------- 1 | = Hello World 2 | 3 | Did you know that famous `Hello World` moniker is 4 | eleven characters long? 5 | 6 | "Hello World".size.assert == 11 7 | 8 | To pass a piece of literal text on with a description 9 | we simply need to end it with a ... 10 | 11 | Now this text will appear verbatim. 12 | In the applique arguments. 13 | 14 | That's all. 15 | 16 | -------------------------------------------------------------------------------- /lib/qed/configure.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # 4 | def self.configure(name=nil, &block) 5 | name = (name || 'default').to_s 6 | profiles[name] = block if block 7 | profiles[name] 8 | end 9 | 10 | # Alias for configure. 11 | def self.profile(name=nil, &block) 12 | configure(name, &block) 13 | end 14 | 15 | # 16 | def self.profiles 17 | @profiles ||= {} 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /demo/10_constant_lookup.md: -------------------------------------------------------------------------------- 1 | # Missing Constant 2 | 3 | If a constant is missing it is because it was not found 4 | in either the demos scope, the applique or at the toplevel. 5 | 6 | begin 7 | UnknownConstant 8 | rescue => err 9 | # no colon means toplevel 10 | err.name.to_s.refute.include?('::') 11 | end 12 | 13 | A constant defined in the applique is visible. 14 | 15 | APPLIQUE_CONSTANT.assert = true 16 | 17 | -------------------------------------------------------------------------------- /demo/samples/data.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 2 | -------------------------------------------------------------------------------- /work/trash/assertion.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # = Assertion 4 | # 5 | # This is the core class of the whole specification system. 6 | # 7 | class Assertion < Exception 8 | 9 | def initialize(message, backtrace=nil) 10 | super(message) 11 | set_backtrace(backtrace) if backtrace 12 | end 13 | 14 | def to_str 15 | message.to_s.strip 16 | end 17 | 18 | end 19 | 20 | end 21 | 22 | # Copyright (c) 2008 Tiger Ops 23 | 24 | -------------------------------------------------------------------------------- /work/experiment/scope.rb: -------------------------------------------------------------------------------- 1 | #require_relative 'assertor' 2 | 3 | module AE 4 | 5 | def self.hello? 6 | "here" 7 | end 8 | 9 | class Assertor < BasicObject 10 | 11 | def compare_message 12 | AE.hello? 13 | end 14 | 15 | end 16 | 17 | end 18 | 19 | def assert 20 | AE::Assertor.new.compare_message 21 | end 22 | 23 | class Scope < Module 24 | 25 | def initialize 26 | super() 27 | end 28 | 29 | def doit 30 | assert 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /demo/08_cross_script.md: -------------------------------------------------------------------------------- 1 | # Cross-Scripting Setup 2 | 3 | We define some variables here to make sure it is 4 | not visible in the next script. 5 | 6 | Let's set two local variables. 7 | 8 | a = 100 9 | b = 200 10 | 11 | And two instance varaibles. 12 | 13 | @a = 1000 14 | @b = 2000 15 | 16 | Also let check how it effect constants. 17 | 18 | CROSS_SCRIPT_CONSTANT = "cross?" 19 | 20 | And a method. 21 | 22 | def cross_script_method 23 | "common" 24 | end 25 | 26 | -------------------------------------------------------------------------------- /work/samples/view_error.rdoc: -------------------------------------------------------------------------------- 1 | = Examples of Failure 2 | 3 | This document is here simply to demonstrate what 4 | a failed and error raising code steps looks like. 5 | 6 | When run with the -v (verbatim) option, for instance, +qed+ 7 | will highlight the following sections in red and give a brief 8 | error message. 9 | 10 | == Failure 11 | 12 | This step demonstrates a failed assertion. 13 | 14 | 1.assert == 2 15 | 16 | == Error 17 | 18 | This step demonstrates a raised error. 19 | 20 | raise "Just because" 21 | 22 | -------------------------------------------------------------------------------- /work/samples/messy.rdoc: -------------------------------------------------------------------------------- 1 | = Compact Documents 2 | 3 | While nicely spaced demonstrandum are nicer to read (IHO) 4 | QED should be able to handle tight-knit demos like this. 5 | 6 | 7 | And what about this. 8 | x = 10 9 | 10 | y = 20 11 | Lets see if the it asserts. 12 | x.assert = 10 13 | Okay. 14 | 15 | And what about this: 16 | 17 | 1 + 1 18 | 19 | How about a text example ... 20 | 21 | This is some shit! 22 | 23 | And another with triple quotes... 24 | 25 | """ 26 | Try this 27 | man 28 | 29 | man 30 | """ 31 | 32 | Okay? 33 | -------------------------------------------------------------------------------- /.ergo/script.rb: -------------------------------------------------------------------------------- 1 | ignore 'work' 2 | 3 | book :index do 4 | rule 'var/*' do 5 | sh 'index -u var' 6 | end 7 | end 8 | 9 | book :test do 10 | # I don't know why this won't work!!! 11 | rule '{demo,lib}/**/*' do 12 | #sh 'qed' 13 | require 'simplecov' 14 | SimpleCov.command_name 'demo' 15 | SimpleCov.start do 16 | add_filter '/demo/' 17 | coverage_dir 'log/coverage' 18 | #add_group "Label", "lib/qed/directory" 19 | end 20 | require 'qed/cli' 21 | QED::Session.cli('-Ilib', 'demo/') 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lib/qed.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # Access to project metadata. 4 | def self.metadata 5 | @metadata ||= ( 6 | require 'yaml' 7 | YAML.load(File.new(File.dirname(__FILE__) + '/qed.yml')) rescue {} 8 | ) 9 | end 10 | 11 | # Access to project metadata as constants. 12 | def self.const_missing(name) 13 | key = name.to_s.downcase 14 | metadata[key] || super(name) 15 | end 16 | 17 | # TODO: Only b/c of Ruby 1.8.x bug. 18 | VERSION = metadata['version'] 19 | 20 | end 21 | 22 | require 'qed/session' 23 | require 'qed/document' 24 | 25 | -------------------------------------------------------------------------------- /work/experiment/run.rb: -------------------------------------------------------------------------------- 1 | module AE 2 | 3 | def self.hello? 4 | "here" 5 | end 6 | 7 | class Assertor < BasicObject 8 | 9 | def compare_message 10 | AE.hello? 11 | end 12 | 13 | def self.const_missing(const) 14 | ::Object.const_get(const) 15 | end 16 | 17 | end 18 | 19 | end 20 | 21 | def assert 22 | AE::Assertor.new.compare_message 23 | end 24 | 25 | class Scope < Module 26 | 27 | def initialize 28 | super() 29 | end 30 | 31 | def doit 32 | p assert 33 | end 34 | 35 | end 36 | 37 | scope = Scope.new 38 | 39 | scope.doit 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | desc "Run test demonstrations" 4 | task 'demo' do 5 | sh "qed" 6 | end 7 | 8 | # NOTE: We can't use the qed simplecov profile in the `.config.rb` 9 | # file b/c simplecov must be loaded before the code it covers. 10 | # So we handle it all here instead. 11 | desc "Run test demonstrations with coverage report" 12 | task 'demo:cov' do 13 | require 'simplecov' 14 | SimpleCov.command_name 'demo' 15 | SimpleCov.start do 16 | coverage_dir 'log/coverage' 17 | #add_group "Label", "lib/qed/directory" 18 | end 19 | require 'qed/cli' 20 | QED::Session.cli 21 | end 22 | 23 | 24 | -------------------------------------------------------------------------------- /etc/qed.rb: -------------------------------------------------------------------------------- 1 | # QED demo configuration 2 | 3 | # This is just an example, we can't actually use it 4 | # for QED itself b/c Simplecov wouldn't be able to 5 | # cover it b/c QED would already be loaded. 6 | QED.configure 'cov' do 7 | require 'simplecov' 8 | SimpleCov.command_name 'demo' 9 | SimpleCov.start do 10 | add_filter '/demo/' 11 | coverage_dir 'log/coverage' 12 | #add_group "Label", "lib/qed/directory" 13 | end 14 | end 15 | 16 | # Just a silly example to try out. 17 | QED.configure 'sample' do 18 | puts ("*" * 78) 19 | puts 20 | 21 | at_exit do 22 | puts 23 | puts ("*" * 78) 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | mailto: 4 | - ruby-talk@ruby-lang.org 5 | - rubyworks-mailinglist@googlegroups.com 6 | 7 | github: 8 | gh_pages: web 9 | 10 | gem: 11 | active: true 12 | 13 | qed: 14 | files: demo/ 15 | 16 | qedoc: 17 | files: demo/ 18 | title: QED Demonstrandum 19 | output: 20 | - web/demo.html 21 | #- DEMO.rdoc 22 | 23 | 24 | yard: 25 | tool : yard 26 | yardopts : true 27 | priority : 2 28 | 29 | locat: 30 | output: log/locat.html 31 | 32 | dnote: 33 | title: Source Notes 34 | labels: ~ 35 | output: log/notes.html 36 | 37 | vclog: 38 | output: 39 | - log/changes.html 40 | - log/history.html 41 | 42 | 43 | -------------------------------------------------------------------------------- /demo/09_cross_script.md: -------------------------------------------------------------------------------- 1 | # Cross-Scripting Check 2 | 3 | Make sure local and instance variables from previous 4 | QED scripts are not visible in this document. 5 | 6 | expect NameError do 7 | a.assert = 100 8 | b.assert = 200 9 | end 10 | 11 | And two instance_varaibles 12 | 13 | @a.assert! == 1000 14 | @b.assert! == 2000 15 | 16 | Method definitions also do not cross QED scripts. 17 | 18 | expect NameError do 19 | cross_script_method 20 | end 21 | 22 | Since each demo is encapsulated in a separated class scope, constants also 23 | do not make their way across. 24 | 25 | expect NameError do 26 | CROSS_SCRIPT_CONSTANT 27 | end 28 | 29 | -------------------------------------------------------------------------------- /demo/helpers/advice.rb: -------------------------------------------------------------------------------- 1 | # This helper is used to demonstrate the use of advice --before, after 2 | # and when clauses. 3 | 4 | count = 0 5 | pudding = [] 6 | 7 | #Before(:import) do 8 | # pudding << "load #{File.basename(__FILE__)}" 9 | #end 10 | 11 | After(:import) do 12 | pudding << "loaded #{File.basename(__FILE__)}" 13 | end 14 | 15 | #Before do 16 | # pudding << :before_step 17 | #end 18 | 19 | #After do 20 | # pudding << :after_step 21 | #end 22 | 23 | When /.*?/ do 24 | count += 1 25 | end 26 | 27 | When /proof is in the pudding/ do 28 | pudding << 'proof' 29 | end 30 | 31 | #When /proof is in the pussing/ do 32 | # pudding << :proof 33 | #end 34 | 35 | # 36 | def prepare_example 37 | "Hello, World!" 38 | end 39 | 40 | -------------------------------------------------------------------------------- /lib/qed/qparser.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # Expiremntal quick parser. 4 | # 5 | # NOT USED YET! 6 | # 7 | class QuickParser #:nodoc: 8 | 9 | # 10 | def initialize(demo) 11 | @lines = demo.lines 12 | end 13 | 14 | # 15 | def parse 16 | flush = true 17 | script = [] 18 | 19 | @lines.each do |line| 20 | case line 21 | when /^\s/ 22 | if flush 23 | script << "Test do\n" 24 | end 25 | script << line 26 | flush = false 27 | else 28 | if !flush 29 | script << "end" 30 | end 31 | script << "# " + line 32 | flush = true 33 | end 34 | end 35 | 36 | script.join() 37 | end 38 | 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /work/samples/rule_step.rdoc: -------------------------------------------------------------------------------- 1 | = Rules 2 | 3 | == Via Code 4 | 5 | We can define's for this script by adding 6 | a rule via code. 7 | 8 | When 'this is cool' do |match, text| 9 | @text = text 10 | end 11 | 12 | Now, let's try it by saying, "this is cool": 13 | 14 | And this is the text. 15 | 16 | Did it work? 17 | 18 | @text.assert == "And this is the text." 19 | 20 | == Via Text 21 | 22 | When: Let #/\w+/ be ... scared of #/\w+/ 23 | 24 | @text = match[0] 25 | @name = match[1] 26 | @monster = match[2] 27 | 28 | Okay let's try it. 29 | 30 | Let John be very scared of Zombies. 31 | 32 | So now what is the name? 33 | 34 | @name.assert == "John" 35 | 36 | What is the monster? 37 | 38 | @monster.assert == "Zombies" 39 | 40 | And the who text? 41 | 42 | @text.assert == "Let John be very scared of Zombies." 43 | 44 | Did it work? 45 | 46 | -------------------------------------------------------------------------------- /Indexfile: -------------------------------------------------------------------------------- 1 | --- 2 | name: 3 | qed 4 | 5 | version: 6 | 2.9.2 7 | 8 | title: 9 | QED 10 | 11 | summary: 12 | Quod Erat Demonstrandum 13 | 14 | description: 15 | QED (Quality Ensured Demonstrations) is a TDD/BDD framework 16 | utilizing Literate Programming techniques. 17 | 18 | requirements: 19 | - ansi 20 | - brass 21 | - ae (test) 22 | - rulebow (build) 23 | #- detroit (build) 24 | 25 | resources: 26 | home: http://rubyworks.github.com/qed 27 | code: http://github.com/rubyworks/qed 28 | bugs: http://github.com/rubyworks/qed/issues 29 | 30 | repositories: 31 | upstream: git://github.com/rubyworks/qed.git 32 | 33 | authors: 34 | - trans 35 | 36 | created: 37 | 2009-06-16 38 | 39 | copyrights: 40 | - (c) 2006 Rubyworks (BSD-2-Clause) 41 | 42 | webcvs: 43 | http://github.com/rubyworks/qed/blob/master/ 44 | -------------------------------------------------------------------------------- /work/defunct/yard-qed.rb: -------------------------------------------------------------------------------- 1 | module YARD::CodeObjects 2 | class QEDFileObject < ExtraFileObject 3 | # 4 | def initialize(dirname) 5 | self.filename = dirname 6 | self.name = File.basename(filename).gsub(/\.[^.]+$/, '').upcase 7 | self.attributes = SymbolHash.new(false) 8 | 9 | files = Dir["#{dirname}/**/*{.rdoc,.md,.qed,.markdown}"] 10 | files = files.reject{ |f| File.directory?(f) } 11 | files = files.sort 12 | contents = files.map{ |f| File.read(f) }.join("\n\n") 13 | 14 | parse_contents(contents) 15 | end 16 | end 17 | end 18 | 19 | module YARD 20 | module CLI 21 | class Yardoc 22 | alias run_without_qed run 23 | def run(*args) 24 | dir = Dir['{qed/,demo/,spec/}'].first.chomp('/') 25 | @options[:files] << CodeObjects::QEDFileObject.new(dir) 26 | run_without_qed(*args) 27 | end 28 | end 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /lib/qed/helpers/file_fixtures.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # This extension provides a simple means for creatind file-system fixtures. 4 | # Include this in your applique, to have a 5 | module FileFixtures 6 | 7 | # 8 | def self.included(base) 9 | require 'erb' 10 | end 11 | 12 | # 13 | def copy_fixture(name, tmpdir=nil) 14 | tmpdir ||= 'tmp' # self.tmpdir 15 | FileUtils.mkdir(tmpdir) unless File.directory?(tmpdir) 16 | srcdir = File.join(demo_directory, 'fixtures', name) 17 | paths = Dir.glob(File.join(srcdir, '**', '*'), File::FNM_DOTMATCH) 18 | paths.each do |path| 19 | basename = File.basename(path) 20 | next if basename == '.' 21 | next if basename == '..' 22 | dest = File.join(tmpdir, path.sub(srcdir+'/', '')) 23 | if File.directory?(path) 24 | FileUtils.mkdir(dest) 25 | else 26 | text = ERB.new(File.read(path)).result 27 | File.open(dest, 'w'){ |f| f << text } 28 | end 29 | end 30 | end 31 | 32 | end 33 | 34 | end 35 | 36 | -------------------------------------------------------------------------------- /work/consider/break/lib/break/break.rb: -------------------------------------------------------------------------------- 1 | module Quarry 2 | 3 | # = Exception Break and Edit 4 | # 5 | class Break 6 | 7 | attr :exception 8 | 9 | alias_method :error, :exception 10 | 11 | # 12 | def initialize(exception) 13 | @exception = exception 14 | end 15 | 16 | # 17 | def edit 18 | file, line = *exception.backtrace[0].split(':') 19 | line = line.to_i 20 | 21 | puts exception 22 | 23 | e = "# DEBUG " + exception.to_s 24 | e.gsub!("`","'") 25 | 26 | e = Regexp.escape(e) 27 | 28 | case ed = ENV['EDITOR'] 29 | when 'vi', 'vim', 'gvim' 30 | cmd = [] 31 | cmd << "#{ed} -e -s #{file} <<-EOS" 32 | cmd << ":#{line}" 33 | cmd << "a" 34 | cmd << "#{e}" 35 | cmd << "." 36 | cmd << ":.,+#{e.size}" 37 | cmd << "EOS" 38 | cmd = cmd.join("\n") 39 | when nil 40 | puts "EDITOR environment variable not set" 41 | else 42 | puts "EDITOR environment variable not supported" 43 | end 44 | 45 | system cmd 46 | end 47 | 48 | end #class Break 49 | 50 | end #module Quarry 51 | 52 | -------------------------------------------------------------------------------- /work/samples/bench.rdoc: -------------------------------------------------------------------------------- 1 | = Water Helper 2 | 3 | WaterHelper is used to determine the the status of water. 4 | To use it, first we need to load the dynamic link library. 5 | 6 | require "WaterHelper.dll" 7 | 8 | For each test will require a new instance of Demo::WaterHelper. 9 | 10 | Before { @instance = Demo::WaterHelper.new } 11 | 12 | First we will show that #is_water_boiled returns true for 100 degress. 13 | 14 | @instance.is_water_boiled(100).assert == true 15 | 16 | And it also returns true for 150 degress. 17 | 18 | @instance.is_water_boiled(150).assert == true 19 | 20 | The method #is_water_frozen returns true for 0 degress. 21 | 22 | @instance.is_water_frozen(0).assert == true 23 | 24 | And it returns true for -50 degress. 25 | 26 | @instance.is_water_frozen(-50).assert == true 27 | 28 | We can also check the water status (i.e. it's phase) 29 | with #get_water_status. It returns Steam for 300 degress. 30 | 31 | @instance.get_water_status(300).assert == "Steam" 32 | 33 | And it returns Liquid for 70 degress. 34 | 35 | @instance.get_water_status(70).assert == "Liquid" 36 | 37 | And lastly it returns Ice for -5 degress. 38 | 39 | @instance.get_water_status(-5).assert == "Ice" 40 | 41 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - Indexfile 6 | authors: 7 | - name: trans 8 | email: transfire@gmail.com 9 | organizations: [] 10 | requirements: 11 | - name: ansi 12 | - name: brass 13 | - groups: 14 | - test 15 | development: true 16 | name: ae 17 | - groups: 18 | - build 19 | development: true 20 | name: rulebow 21 | conflicts: [] 22 | alternatives: [] 23 | resources: 24 | - type: home 25 | uri: http://rubyworks.github.com/qed 26 | label: Homepage 27 | - type: code 28 | uri: http://github.com/rubyworks/qed 29 | label: Source Code 30 | - type: bugs 31 | uri: http://github.com/rubyworks/qed/issues 32 | label: Issue Tracker 33 | repositories: 34 | - name: upstream 35 | scm: git 36 | uri: git://github.com/rubyworks/qed.git 37 | categories: [] 38 | copyrights: 39 | - holder: Rubyworks 40 | year: '2006' 41 | license: BSD-2-Clause 42 | customs: [] 43 | paths: 44 | lib: 45 | - lib 46 | name: qed 47 | title: QED 48 | version: 2.9.2 49 | summary: Quod Erat Demonstrandum 50 | description: QED (Quality Ensured Demonstrations) is a TDD/BDD framework utilizing 51 | Literate Programming techniques. 52 | created: '2009-06-16' 53 | webcvs: http://github.com/rubyworks/qed/blob/master/ 54 | date: '2015-03-01' 55 | -------------------------------------------------------------------------------- /work/trash/grammar/should.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | require 'qed/expectation' 4 | 5 | module Grammar 6 | 7 | # = Should Nomenclature 8 | # 9 | # The term *should* has become the defacto standard for 10 | # BDD assertions, so Quarry supports this nomenclature. 11 | # 12 | module Should 13 | 14 | # Same as #expect but only as a functor. 15 | # 16 | # 4.should == 3 #=> Expectation Error 17 | # 18 | def should 19 | return Expectation.new(self, :backtrace=>caller) 20 | end 21 | 22 | # Designate a negated expectation via a *functor*. 23 | # Read this as "should not". 24 | # 25 | # 4.should! == 4 #=> Expectation Error 26 | # 27 | # See also #expect! 28 | # 29 | def should! 30 | return Expectation.new(self, :negate=>true, :backtrace=>caller) 31 | end 32 | 33 | # See #should! method. 34 | # 35 | alias_method :should_not, :should! 36 | 37 | # 38 | #alias_method :should_raise, :assert_raises 39 | 40 | # 41 | #alias_method :should_not_raise, :assert_raises! 42 | 43 | end 44 | 45 | end 46 | 47 | class ::Object #:nodoc: 48 | include Grammar::Should 49 | end 50 | 51 | end 52 | 53 | -------------------------------------------------------------------------------- /work/defunct/config.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | =begin 4 | # Setup global configuration. 5 | # 6 | # QED.config do 7 | # 8 | # Before(:session) do 9 | # # ... 10 | # end 11 | # 12 | # After(:session) do 13 | # # ... 14 | # end 15 | # 16 | # end 17 | # 18 | def self.configure(&block) 19 | @config ||= Profile.new #(nil) 20 | @config.instance_eval(&block) if block 21 | @config 22 | end 23 | =end 24 | 25 | # 26 | class Profile 27 | 28 | # 29 | def initialize 30 | #@local = ['test/demos', 'demos', 'qed'] 31 | 32 | @before = { :session=>[], :demo=>[], :step=>[] } 33 | @after = { :session=>[], :demo=>[], :step=>[] } 34 | 35 | #if file = Dir.glob('{.,}config/qed.{yml,yaml}').first 36 | # YAML.load(File.new(file)).each do |k,v| 37 | # __send__("#{k}=", v) 38 | # end 39 | #end 40 | end 41 | 42 | # 43 | #attr_accessor :local 44 | 45 | # 46 | def Before(type=:session, &procedure) 47 | @before[type] << procedure if procedure 48 | @before[type] 49 | end 50 | 51 | # 52 | def After(type=:session, &procedure) 53 | @after[type] << procedure if procedure 54 | @after[type] 55 | end 56 | 57 | end 58 | 59 | end 60 | 61 | -------------------------------------------------------------------------------- /demo/05_quote.md: -------------------------------------------------------------------------------- 1 | # Quotes 2 | 3 | We do not always want verbatim clauses to be interpreted as code. 4 | Sometimes it would more useful to treat them a plain text to 5 | which the preceeding paragraph can make use in a processing rule. 6 | 7 | For example let say we want to make an example out of the following 8 | text: 9 | 10 | The file will contain 11 | 12 | this text 13 | 14 | The use of the colon (`:`) tells the processor that the next 15 | segment is a plain text continuation of the current segment, rather 16 | than executable code. If the next segment is varbatim it will be added to 17 | the end of the arguments list of any applicable processing rule. 18 | 19 | Behind the scenes we created a rule to set the text to an instance 20 | variable called @quote_text, and we can verify it is so. 21 | 22 | @quote_text.assert == "The file will contain\n\nthis text" 23 | 24 | Alternately we can use a colon (':') instead of ellipsis. We can repeat 25 | the same statment as above. 26 | 27 | For example let say we want to make an example out of the following 28 | text: 29 | 30 | The file will contain 31 | 32 | different text 33 | 34 | And again we can verify that it did in fact set the @quote_text variable. 35 | 36 | @quote_text.assert == "The file will contain\n\ndifferent text" 37 | 38 | -------------------------------------------------------------------------------- /demo/07_toplevel.md: -------------------------------------------------------------------------------- 1 | # Toplevel Simulation 2 | 3 | QED simulates Ruby's TOPLEVEL environment in both the Demonstrandum 4 | and the Applique contexts. This serves two important purposes. 5 | First, it provides the tester the environment that is most intutive. 6 | And second, and more importantly, it stays out of the actual 7 | TOPLEVEL space to prevent any potential interferece with any of 8 | the code it is intended to test. 9 | 10 | Let's look at some examples. For starters, we have access to a class 11 | defined at the "toplevel" in the applique. 12 | 13 | ToplevelClass 14 | 15 | We can also call a method defined in the toplevel. 16 | 17 | toplevel_method.assert == true 18 | 19 | At the demonstrandum level we can define reusable methods. 20 | 21 | def demo_method 22 | true 23 | end 24 | 25 | demo_method.assert == true 26 | 27 | And at the demonstrandum level even singleton methods are accessible. 28 | 29 | def self.singleton_method; true; end 30 | 31 | singleton_method.assert == true 32 | 33 | QED uses a self-extend modules to achieve this simulation, so the 34 | contexts are in fact a bit more capable then even Ruby's TOPLEVEL. 35 | For instance, #define_method can be used. 36 | 37 | define_method(:named_method){ true } 38 | 39 | named_method.assert == true 40 | 41 | -------------------------------------------------------------------------------- /work/consider/scope_study.rb: -------------------------------------------------------------------------------- 1 | class S < Module 2 | 3 | def initialize 4 | super() 5 | extend self 6 | define_method(:__binding__) do 7 | @binding ||= binding 8 | end 9 | end 10 | 11 | #def __binding__ 12 | # @binding ||= binding 13 | #end 14 | 15 | end 16 | 17 | 18 | # Sample 1 19 | 20 | s1 = S.new 21 | 22 | s1.module_eval <<-END 23 | puts "\ns1" 24 | p self 25 | p self.class 26 | p self.object_id 27 | X = 10 28 | def x; 10; end 29 | END 30 | 31 | raise unless s1.x == 10 rescue puts "s1.x " + $! 32 | raise unless s1::X == 10 rescue puts "s1::X " + $! 33 | 34 | 35 | # Sample 2 36 | 37 | s2 = S.new 38 | 39 | eval(<<-END, s2.__binding__) 40 | puts "\ns2" 41 | p self 42 | p self.class 43 | p self.object_id 44 | X = 10 45 | def x; 10; end 46 | def q; 20; end 47 | END 48 | 49 | raise unless s2.x == 10 rescue puts "s2.x " + $! 50 | raise unless s2.q == 20 rescue puts "s2.y " + $! 51 | raise unless s2::X == 10 rescue puts "s2::X " + $! 52 | 53 | 54 | # Sample 3 55 | 56 | s3 = S.new 57 | 58 | eval(<<-END, s3.__binding__) 59 | puts "\ns3" 60 | p self 61 | p self.class 62 | p self.object_id 63 | END 64 | 65 | raise unless s3.x == 10 rescue puts "s3.x " + $! 66 | raise unless s3.q == 20 rescue puts "s3.y " + $! 67 | raise unless s3::X == 10 rescue puts "s3::X " + $! 68 | 69 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD-2-Clause License 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY Thomas Sawyer ``AS IS'' AND ANY EXPRESS 14 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 16 | NO EVENT SHALL Thomas Sawyer OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 17 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 18 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | (SPDX: BSD-2-Clause) 25 | 26 | -------------------------------------------------------------------------------- /demo/11_embedded_rules.md: -------------------------------------------------------------------------------- 1 | # Meta Code 2 | 3 | All code steps are evaluated in a rescue clause. If an error occurs, it 4 | is captured and reported through the test report, and execution continues. 5 | However, sometimes this is not desired. To evaluate a step without the 6 | rescue clause, and effective *fail fast*, append `^` mark to the end of 7 | the desription text, like so. ^ 8 | 9 | When 'this is cool' do |text| 10 | @text = text 11 | end 12 | 13 | Now, let's try it by saying, "this is cool": 14 | 15 | And this is the text. 16 | 17 | Did it work? 18 | 19 | @text.assert == "And this is the text." 20 | 21 | 22 | ## Match Separator 23 | 24 | The `When` method can take a list of String or Regexp as arguments. 25 | If any of the strings contain `...`, the string will be split into 26 | two at this point, which effective means that any text can occur 27 | within this space. It behaves much like adding `((*.?))`, but parses 28 | more quickly by dividing the string into multiple matches. 29 | 30 | When 'Let /(\w+)/ be ... scared of /(\w+)/' do |name, monster| 31 | @name = name 32 | @monster = monster 33 | end 34 | 35 | Okay let's try it: Let John be very scared of Zombies. 36 | 37 | So now what is the name? 38 | 39 | @name.assert == "John" 40 | 41 | What is the monster? 42 | 43 | @monster.assert == "Zombies" 44 | 45 | Did it work? 46 | 47 | -------------------------------------------------------------------------------- /demo/01_demos.md: -------------------------------------------------------------------------------- 1 | # QED Demonstrandum 2 | 3 | ## Steps 4 | 5 | QED demos are light-weight specification documents, highly suitable 6 | to interface-driven design. The documents are divided up into 7 | steps separated by blank lines. Steps that are flush to the 8 | left margin are always explanatory comments. Indented steps are 9 | either executable code or plain text samples. 10 | 11 | Each step is executed in order of appearance within a rescue wrapper 12 | that captures any failures or errors. If neither a failure or error 13 | occur then the step gets a "pass". 14 | 15 | For example, the following passes. 16 | 17 | (2 + 2).assert == 4 18 | 19 | While the following would "fail", as indicated by the raising of 20 | an Assertion error. 21 | 22 | expect Assertion do 23 | (2 + 2).assert == 5 24 | end 25 | 26 | And this would have raised a NameError. 27 | 28 | expect NameError do 29 | nobody_knows_method 30 | end 31 | 32 | ## Defining Custom Assertions 33 | 34 | The context in which the QED code is run is a self-extended module, thus 35 | reusable macros can be created simply by defining a method. 36 | 37 | def assert_integer(x) 38 | x.assert.is_a? Integer 39 | end 40 | 41 | Now lets try out our new macro definition. 42 | 43 | assert_integer(4) 44 | 45 | Let's prove that it can also fail. 46 | 47 | expect Assertion do 48 | assert_integer("IV") 49 | end 50 | 51 | -------------------------------------------------------------------------------- /lib/qed/document/markup.rb: -------------------------------------------------------------------------------- 1 | require 'rdoc/markup' 2 | require 'rdoc/markup/to_html' 3 | 4 | module QED 5 | 6 | class Document 7 | 8 | # = QED Document Markup 9 | # 10 | # QED Document Markup is based on RDoc's SimpleMarkup format but adds 11 | # some additional features. 12 | # 13 | # * `[no-spaces]` produces [no-space]. 14 | # 15 | # FIXME: Can't get `brackets` to work. 16 | class Markup 17 | 18 | def initialize(text, options={}) 19 | @text = text 20 | end 21 | 22 | def to_html 23 | parser.convert(@text, formatter) 24 | end 25 | 26 | def parser 27 | @parser ||= ( 28 | m = RDoc::Markup.new 29 | #p.add_word_pair("{", "}", :STRIKE) 30 | #p.add_html("no", :STRIKE) 31 | #p.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) 32 | #m.add_word_pair('`', '`', :CODE) 33 | m.add_special(/\`(\b.*?)\`/, :CODE) 34 | m 35 | ) 36 | end 37 | 38 | def formatter 39 | @formatter ||= ( 40 | f = ToHTML.new 41 | #f.add_tag(:STRIKE, "", "") 42 | f.add_tag(:CODE, "", "") 43 | f 44 | ) 45 | end 46 | 47 | # Formatter 48 | class ToHTML < RDoc::Markup::ToHtml 49 | def handle_special_CODE(special) 50 | "" + special.text.sub('`','').chomp('`') + "" 51 | end 52 | end 53 | 54 | end 55 | 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast -x *.lock .index .ruby .yardopts bin demo lib man spec test task *.txt *.md *.rdoc 2 | .index 3 | .yardopts 4 | bin/qed 5 | bin/qedoc 6 | demo/01_demos.md 7 | demo/02_advice.md 8 | demo/03_helpers.md 9 | demo/04_samples.md 10 | demo/05_quote.md 11 | demo/07_toplevel.md 12 | demo/08_cross_script.md 13 | demo/09_cross_script.md 14 | demo/10_constant_lookup.md 15 | demo/11_embedded_rules.md 16 | demo/99_issues/02_topcode.md 17 | demo/applique/ae.rb 18 | demo/applique/constant.rb 19 | demo/applique/env.rb 20 | demo/applique/fileutils.rb 21 | demo/applique/markup.rb 22 | demo/applique/quote.md 23 | demo/applique/toplevel.rb 24 | demo/helpers/advice.rb 25 | demo/helpers/toplevel.rb 26 | demo/samples/data.txt 27 | demo/samples/table.yml 28 | lib/qed/applique.rb 29 | lib/qed/cli/qed.rb 30 | lib/qed/cli/qedoc.rb 31 | lib/qed/cli.rb 32 | lib/qed/configure.rb 33 | lib/qed/core_ext.rb 34 | lib/qed/demo.rb 35 | lib/qed/document/jquery.js 36 | lib/qed/document/markup.rb 37 | lib/qed/document/template.rhtml 38 | lib/qed/document.rb 39 | lib/qed/evaluator.rb 40 | lib/qed/helpers/file_fixtures.rb 41 | lib/qed/helpers/shell_session.rb 42 | lib/qed/parser.rb 43 | lib/qed/qparser.rb 44 | lib/qed/reporter/abstract.rb 45 | lib/qed/reporter/dotprogress.rb 46 | lib/qed/reporter/html.rb 47 | lib/qed/reporter/linear.rb 48 | lib/qed/reporter/tapy.rb 49 | lib/qed/reporter/verbatim.rb 50 | lib/qed/scope.rb 51 | lib/qed/session.rb 52 | lib/qed/settings.rb 53 | lib/qed/step.rb 54 | lib/qed/utils.rb 55 | lib/qed.rb 56 | lib/qed.yml 57 | LICENSE.txt 58 | HISTORY.md 59 | README.md 60 | -------------------------------------------------------------------------------- /lib/qed/cli/qedoc.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | require 'optparse' 4 | require 'qed/document' 5 | 6 | class Document 7 | 8 | # Command line interface for generating qedocs. 9 | def self.cli(*argv) 10 | 11 | options = {} 12 | 13 | parser = OptionParser.new do |usage| 14 | usage.banner = "Usage: qedoc [OPTIONS] [ ... ]" 15 | 16 | usage.on("-o", "--output [DIR]", "Output directory") do |dir| 17 | options[:output]= dir 18 | end 19 | 20 | usage.on("-t", "--title [TITLE]", "Title of Document") do |title| 21 | options[:title]= title 22 | end 23 | 24 | usage.on("--css [URI]", "Specify a URI for a CSS file add to HTML header.") do |uri| 25 | options[:css] = uri 26 | end 27 | 28 | usage.on("--dryrun", "") do 29 | options[:dryrun] = true 30 | end 31 | 32 | usage.on("-q", "--quiet", "") do 33 | options[:quiet] = true 34 | end 35 | 36 | usage.on_tail("-h", "--help", "display this help message") do 37 | puts usage 38 | exit 39 | end 40 | end 41 | 42 | parser.parse!(argv) 43 | 44 | options[:paths] = argv.dup 45 | 46 | #opts[:output] = cli.options[:file] 47 | #opts[:dryrun] = cli.options[:dryrun] 48 | #opts[:quiet] = cli.options[:quiet] 49 | #opts[:css] = cli.options[:css] 50 | #opts[:title] = cli.options[:title] 51 | 52 | doc = QED::Document.new(options) 53 | 54 | doc.generate 55 | end 56 | 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /demo/03_helpers.md: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | There are two ways to load advice scripts. Manually loaded 4 | helpers act pe-demonstrandum and so apply only to the currently 5 | executing demo. Automaticly loaded helpers apply to all 6 | demonstrandum within their preview. 7 | 8 | Helper scripts can be written just like demonstration scripts, 9 | or they can be defined as pure Ruby scripts. 10 | 11 | ## Automatic Helpers 12 | 13 | Automatic helpers, known as the "applique" are loaded at the 14 | start of a session and apply equally to all demonstrandum within 15 | the same or lower directory as the demo. These helpers are placed 16 | in an +applique+ subdirectory. For instance this document uses, 17 | [applique/env.rb](applique/env.rb). 18 | 19 | ## Manual Helpers 20 | 21 | Manual helpers are loaded per-demonstration by using specially 22 | marked links. 23 | 24 | For example, because this link, [Advice](qed://helpers/advice.rb), 25 | begins with `qed:`, it will be used to load a helper. We can 26 | see this with the following assertion. 27 | 28 | pudding.assert.include?('loaded advice.rb') 29 | 30 | No where in the demonstration have we defined +pudding+, but 31 | it has been defined for us in the advice.rb helper script. 32 | 33 | We can also see that the generic When clause in our advice 34 | helper is keeping count of decriptive paragraphs. Since the 35 | helper script was loaded two paragraphs back, the next count 36 | will be 3. 37 | 38 | count.assert == 3 39 | 40 | Helpers are vital to building test-demonstration suites for 41 | applications. But here again, only use them as necessary. 42 | The more helpers you use the more difficult your demos will 43 | be to follow. 44 | 45 | -------------------------------------------------------------------------------- /work/trash/expectation.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | require 'qed/assertion' 4 | 5 | # = Expectation 6 | # 7 | # Expectation is an Assertion Functor. 8 | # 9 | class Expectation 10 | 11 | hide = instance_methods.select{ |m| m.to_s !~ /^__/ } 12 | hide.each{ |m| protected m } 13 | 14 | private 15 | 16 | # New Expectation. 17 | # 18 | def initialize(delegate, ioc={}) #, backtrace) 19 | @delegate = delegate 20 | @negate = ioc[:negate] 21 | @message = ioc[:message] 22 | @backtrace = ioc[:backtrace] || caller #[1..-1] 23 | end 24 | 25 | # Converts missing method into an Assertion. 26 | # 27 | def method_missing(sym, *a, &b) 28 | test = @delegate.__send__(sym, *a, &b) 29 | 30 | if (@negate ? test : !test) 31 | msg = @message || __msg__(sym, *a, &b) 32 | error = Assertion.new(msg) 33 | error.set_backtrace(@backtrace) 34 | raise error 35 | end 36 | end 37 | 38 | # Puts together a suitable error message. 39 | # 40 | def __msg__(m, *a, &b) 41 | if @negate 42 | "! #{@delegate.inspect} #{m} #{a.collect{|x| x.inspect}.join(',')}" 43 | else 44 | "#{@delegate.inspect} #{m} #{a.collect{|x| x.inspect}.join(',')}" 45 | end 46 | #self.class.message(m)[@delegate, *a] ) 47 | end 48 | 49 | # TODO: Ultimately better messages would be nice ? 50 | # 51 | #def self.message(op,&block) 52 | # @message ||= {} 53 | # block ? @message[op.to_sym] = block : @message[op.to_sym] 54 | #end 55 | # 56 | #message(:==){ |*a| "Expected #{a[0].inspect} to be equal to #{a[1].inspect}" } 57 | end 58 | 59 | end 60 | 61 | -------------------------------------------------------------------------------- /lib/qed/reporter/dotprogress.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | module Reporter #:nodoc: 3 | 4 | require 'qed/reporter/abstract' 5 | 6 | # The dot reporter is the traditional test reporter where 7 | # dot is printed for every successful step, an 'E' for 8 | # errors and an 'F' for failures. 9 | # 10 | class Dot < Abstract 11 | 12 | # 13 | def before_session(session) 14 | @start_time = Time.now 15 | io.puts "Started" 16 | end 17 | 18 | # 19 | #def before_step(step) 20 | # super(step) 21 | # io.print "." 22 | # io.flush 23 | #end 24 | 25 | def pass(step) 26 | io.print "." 27 | io.flush 28 | super(step) 29 | end 30 | 31 | def fail(step, assertion) 32 | io.print "F".ansi(:red) 33 | io.flush 34 | super(step, assertion) 35 | end 36 | 37 | def error(step, exception) 38 | io.print "E".ansi(:red) 39 | io.flush 40 | super(step, exception) 41 | end 42 | 43 | # 44 | def after_session(session) 45 | print_time 46 | 47 | errors.each do |step, exception| 48 | backtrace = sane_backtrace(exception) 49 | 50 | io.puts "***** ERROR *****".ansi(:red) 51 | io.puts "#{exception}" 52 | backtrace.each do |bt| 53 | io.puts bt 54 | io.puts code_snippet(bt) 55 | end 56 | io.puts 57 | end 58 | 59 | fails.each do |step, assertion| 60 | backtrace = sane_backtrace(assertion) 61 | 62 | io.puts "***** FAIL *****".ansi(:red, :bold) 63 | io.puts "#{assertion}" 64 | backtrace.each do |bt| 65 | io.puts bt 66 | io.puts code_snippet(bt) 67 | end 68 | io.puts 69 | end 70 | 71 | print_tally 72 | end 73 | 74 | end#class DotProgress 75 | 76 | end#module Reporter 77 | end#module QED 78 | 79 | -------------------------------------------------------------------------------- /demo/04_samples.md: -------------------------------------------------------------------------------- 1 | # Test Samples 2 | 3 | ## Flat-file Data 4 | 5 | When creating testable demonstrations, there are times when sizable 6 | chunks of data are needed. It is convenient to store such data in 7 | separate files. The +Data+ method makes is easy to utilize them. 8 | 9 | Data(File.dirname(__FILE__) + '/samples/data.txt').assert =~ /dolor/ 10 | 11 | The +Data+ method can also take a block which passes the data 12 | as the blocks only argument. 13 | 14 | Data(File.dirname(__FILE__) + '/samples/data.txt') do |data| 15 | data.assert =~ /dolor/ 16 | end 17 | 18 | Files are looked-up relative to the location of the current document. 19 | If not found then they will be looked-up relative to the current 20 | working directory. 21 | 22 | ## Tabular Data 23 | 24 | The +Table+ method is similar to the +Data+ method except that it 25 | expects a YAML file, and it can take a block to iterate the data over. 26 | This makes it easy to test tables of examples. 27 | 28 | The arity of the table block corresponds to the number of columns in 29 | each row of the table. Each row is assigned in turn and run through 30 | the coded step. Consider the following example. 31 | 32 | Every row in the [table.yml table](table.yml) will be assigned to 33 | the block parameters and run through the subsequent assertion. 34 | 35 | Table File.dirname(__FILE__) + '/samples/table.yml' do |x, y| 36 | x.upcase.assert == y 37 | end 38 | 39 | Without the block, the +Table+ methods simply returns the sample data. 40 | 41 | ## Considerations 42 | 43 | Both Data and Table are some what "old fashion" approches to sample 44 | data. New techinques using plain text blocks are more convenient 45 | in that the data can be stored directly in the demonstration itself. 46 | However, for especially large data sets and external file is still 47 | the better option, and +Data+ and +Table+ make them quite easy to 48 | access. 49 | 50 | -------------------------------------------------------------------------------- /lib/qed/core_ext.rb: -------------------------------------------------------------------------------- 1 | require 'brass' 2 | 3 | class Object 4 | 5 | unless method_defined?(:instance_exec) # 1.9 6 | require 'thread' 7 | 8 | module InstanceExecMethods #:nodoc: 9 | end 10 | 11 | include InstanceExecMethods 12 | 13 | # Evaluate the block with the given arguments within the context of 14 | # this object, so self is set to the method receiver. 15 | # 16 | # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec 17 | # 18 | # This version has been borrowed from Rails for compatibility sake. 19 | def instance_exec(*args, &block) 20 | begin 21 | old_critical, Thread.critical = Thread.critical, true 22 | n = 0 23 | n += 1 while respond_to?(method_name = "__instance_exec#{n}") 24 | InstanceExecMethods.module_eval { define_method(method_name, &block) } 25 | ensure 26 | Thread.critical = old_critical 27 | end 28 | 29 | begin 30 | send(method_name, *args) 31 | ensure 32 | InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil 33 | end 34 | end 35 | end 36 | 37 | # 38 | # This is used by the `#=>` notation. 39 | # 40 | def must_return(value) 41 | assert(self == value, "#{self.inspect} #=> #{value.inspect}") 42 | end 43 | 44 | end 45 | 46 | class String 47 | 48 | # from facets 49 | def tabto(num=nil, opts={}) 50 | raise ArgumentError, "String#margin has been renamed to #trim." unless num 51 | 52 | tab = opts[:tab] || 2 53 | str = gsub("\t", " " * tab) # TODO: only leading tabs ? 54 | 55 | if opts[:lead] 56 | if self =~ /^( *)\S/ 57 | indent(num - $1.length) 58 | else 59 | self 60 | end 61 | else 62 | min = [] 63 | str.each_line do |line| 64 | next if line.strip.empty? 65 | min << line.index(/\S/) 66 | end 67 | min = min.min 68 | str.indent(num - min) 69 | end 70 | end 71 | 72 | # from facets 73 | def indent(n, c=' ') 74 | if n >= 0 75 | gsub(/^/, c * n) 76 | else 77 | gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "") 78 | end 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /work/defunct/doubles/stub.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # = Stub 4 | # 5 | class Stub < Module 6 | attr :object 7 | 8 | def initialize 9 | super() 10 | @table = {} 11 | end 12 | 13 | # 14 | def __table__ ; @table ; end 15 | 16 | # 17 | def method_missing(meth, *args, &block) 18 | table = @table 19 | interface = [meth, args, block_given?] 20 | 21 | table[interface] = nil 22 | 23 | define_method(meth) do |*args| 24 | table[interface] 25 | end 26 | 27 | Setter.new(table, interface) 28 | end 29 | 30 | # 31 | class Setter 32 | def initialize(table, interface) 33 | @table = table 34 | @interface = interface 35 | end 36 | 37 | def ==(result) 38 | @table[@interface] = result 39 | end 40 | end#class Setter 41 | 42 | # = Stub::Delegator 43 | # 44 | class Delegator 45 | instance_methods(true).each{ |m| protected m unless m.to_s =~ /^__/ } 46 | 47 | def initialize(object, stub_module) 48 | @instance_delegate = object 49 | extend(stub_module) 50 | end 51 | 52 | def method_missing(s, *a, &b) 53 | @instance_delegate.__send__(s, *a, &b) 54 | end 55 | end 56 | 57 | end#class Stub 58 | 59 | class ::Object 60 | 61 | # Create a new stub. 62 | def stub(stub_module=nil) 63 | if stub_module 64 | Stub::Delegator.new(self, stub_module) 65 | else 66 | @_stub ||= Stub.new 67 | extend(@_stub) 68 | @_stub 69 | end 70 | end 71 | 72 | # We can't remove the module per-say. So we have to 73 | # just neuter it. This is a very weak solution, but 74 | # it will suffice for the moment. 75 | #-- 76 | # TODO: Use Carats for #unmix. 77 | #++ 78 | def remove_stub(stub_module=nil) 79 | stub_module ||= @_stub 80 | obj = self 81 | mod = Module.new 82 | stub_module.__table__.each do |interface, result| 83 | meth = interface[0] 84 | mod.module_eval do 85 | define_method(meth, &obj.class.instance_method(meth).bind(obj)) 86 | end 87 | end 88 | extend(mod) 89 | end 90 | 91 | end#class ::Object 92 | 93 | end#module Quarry 94 | 95 | -------------------------------------------------------------------------------- /work/defunct/doubles/mock.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # = Mock 4 | # 5 | class Mock < Module 6 | attr :object 7 | 8 | def initialize 9 | super() 10 | @table = {} 11 | end 12 | 13 | # 14 | def __table__ ; @table ; end 15 | 16 | # TODO: Ruby has retry, but I need continue! 17 | def method_missing(meth, *args, &block) 18 | table = @table 19 | interface = [meth, args, block_given?] 20 | 21 | table[interface] = nil 22 | 23 | define_method(meth) do |*args| 24 | result = super 25 | result.assert == table[interface] 26 | return result 27 | end 28 | 29 | Setter.new(table, interface) 30 | end 31 | 32 | # 33 | class Setter 34 | def initialize(table, interface) 35 | @table = table 36 | @interface = interface 37 | end 38 | 39 | def ==(result) 40 | @table[@interface] = result 41 | end 42 | end 43 | 44 | # = Mock::Delegator 45 | # 46 | class Delegator 47 | instance_methods(true).each{ |m| protected m unless m.to_s =~ /^__/ } 48 | 49 | def initialize(object, mock_module) 50 | @instance_delegate = object 51 | extend(mock_module) 52 | end 53 | 54 | def method_missing(s, *a, &b) 55 | @instance_delegate.__send__(s, *a, &b) 56 | end 57 | end#class Delegator 58 | 59 | end#class Mock 60 | 61 | class ::Object 62 | # Create mock object. 63 | def mock(mock_module=nil) 64 | if mock_module 65 | Mock::Delegator.new(self, mock_module) 66 | else 67 | @_mock ||= Mock.new 68 | extend(@_mock) 69 | @_mock 70 | end 71 | end 72 | 73 | # We can't remove the module per-say. So we have to 74 | # just neuter it. This is a very weak solution, but 75 | # it will suffice for the moment. 76 | #-- 77 | # TODO: Use Carats for #unmix. 78 | #++ 79 | def remove_mock(mock_module=nil) 80 | mock_module ||= @_mock 81 | obj = self 82 | mod = Module.new 83 | mock_module.__table__.each do |interface, result| 84 | meth = interface[0] 85 | mod.module_eval do 86 | define_method(meth, &obj.class.instance_method(meth).bind(obj)) 87 | end 88 | end 89 | extend(mod) 90 | end 91 | end#class ::Object 92 | 93 | end#module Quarry 94 | 95 | -------------------------------------------------------------------------------- /lib/qed/reporter/linear.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | module Reporter #:nodoc: 3 | 4 | require 'ansi/terminal' 5 | require 'qed/reporter/abstract' 6 | 7 | # Linear reporter limits each step to a single line. 8 | # 9 | class Linear < Abstract 10 | 11 | # 12 | def before_session(session) 13 | @width = ANSI::Terminal.terminal_width - 12 14 | @start_time = Time.now 15 | puts "[INFO] Session @ #{Time.now}".ansi(:bold) 16 | end 17 | 18 | # 19 | def before_demo(demo) 20 | file = localize_file(demo.file) 21 | puts "[DEMO] #{file}".ansi(:bold) 22 | end 23 | 24 | # 25 | def before_applique(step) 26 | super(step) 27 | #post 28 | str = "[NOTE] #{step.explain.gsub(/\s+/,' ')} "[0,@width] 29 | pad = @width - str.size + 1 30 | print str + (' ' * pad) 31 | puts "[#{timestamp}]" 32 | end 33 | 34 | # 35 | def pass(step) 36 | super(step) 37 | 38 | print_step(step, 'PASS', :green) 39 | 40 | #s = [] 41 | #s << "PASS".ansi(:green) 42 | #puts s.join("\n") 43 | end 44 | 45 | # 46 | def fail(step, assertion) 47 | super(step, assertion) 48 | 49 | print_step(step, 'FAIL', :red) 50 | 51 | #puts "FAIL".ansi(:red) 52 | 53 | s = [] 54 | s << assertion.class.name 55 | s << assertion.message 56 | 57 | backtrace = sane_backtrace(assertion) 58 | backtrace.each do |bt| 59 | s << bt 60 | s << code_snippet(bt) 61 | end 62 | 63 | puts s.join("\n").tabto(8) 64 | end 65 | 66 | # 67 | def error(step, exception) 68 | super(step, exception) 69 | 70 | print_step(step, 'ERRO', :red) 71 | 72 | #puts "ERROR".ansi(:red) 73 | 74 | s = [] 75 | s << assertion.class.name 76 | s << assertion.message 77 | 78 | backtrace = sane_backtrace(assertion) 79 | backtrace.each do |bt| 80 | s << bt 81 | s << code_snippet(bt) 82 | end 83 | 84 | puts s.join("\n").tabto(8) 85 | end 86 | 87 | # 88 | def after_session(session) 89 | puts "[INFO] %s demos, %s steps: %s failures, %s errors (%s/%s assertions)" % get_tally 90 | puts "[INFO] Finished in %.5f seconds." % [Time.now - @start_time] 91 | puts "[INFO] End Session @ #{Time.now}".ansi(:bold) 92 | end 93 | 94 | private 95 | 96 | def timestamp 97 | (Time.now - @start_time).to_s[0,8] 98 | end 99 | 100 | # 101 | def print(str) 102 | @appendable = true 103 | io.print str 104 | end 105 | 106 | # 107 | def puts(str) 108 | @appendable = false 109 | io.puts str 110 | end 111 | 112 | # 113 | def post 114 | io.puts if @appendable 115 | @appendable = false 116 | end 117 | 118 | # 119 | def print_step(step, stat, *color) 120 | desc = step.explain.gsub(/\s+/,' ') 121 | if desc.start_with?('=') or desc.start_with?('#') 122 | desc = desc.ansi(:magenta) 123 | end 124 | str = "[#{stat}] #{desc} "[0,@width] 125 | pad = @width - str.unansi.size + 1 126 | print (str + (' ' * pad)).ansi(*color) 127 | puts "[#{timestamp}]" 128 | end 129 | end 130 | 131 | end#module Reporter 132 | end#module QED 133 | -------------------------------------------------------------------------------- /work/trash/grammar/assert.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | require 'qed/expectation' 4 | 5 | module Grammar 6 | 7 | # = Assert Nomenclature 8 | # 9 | module Assert 10 | 11 | # Assert a operational relationship. 12 | # 13 | # 4.assert == 3 14 | # 15 | # If only a single test argument is given then 16 | # #assert simple validates that it evalutate to true. 17 | # An optional message argument can be given in this 18 | # case which will be used instead of the deafult message. 19 | # 20 | # assert(4==3, "not the same thing") 21 | # 22 | # In block form, #assert ensures the block evalutes 23 | # truthfully, i.e. not as nil or false. 24 | # 25 | # assert{ 4==3 } 26 | # 27 | # If an argument is given with a block, #assert compares 28 | # the argument to the result of evaluating the block. 29 | # 30 | # assert(4){ 3 } 31 | # 32 | # #assert compares the expected value and the actual 33 | # value with regular equality #==. 34 | # 35 | def assert(test=Exception, msg=nil, &block) 36 | if block 37 | act = block.call 38 | if test == Exception 39 | raise Assertion.new(msg, caller) unless act 40 | else 41 | yes = (test == act) 42 | msg = "#{exp}.equate? #{act}" unless msg 43 | raise Assertion.new(msg, caller) unless yes 44 | end 45 | elsif test != Exception 46 | msg = "failed assertion (no message given)" unless msg 47 | raise Assertion.new(msg, caller) unless test 48 | else 49 | return Expectation.new(self, :backtrace=>caller) 50 | end 51 | end 52 | 53 | # Assert not an operational relationship. 54 | # Read it as "assert not". 55 | # 56 | # 4.assert! == 4 57 | # 58 | # See #assert. 59 | # 60 | # AUHTOR'S NOTE: This method would not be necessary 61 | # if Ruby would allow +!=+ to be define as a method, 62 | # or at least +!+ as a unary method. 63 | # 64 | def assert!(test=Exception, msg=nil, &block) 65 | if block 66 | act = block.call 67 | if test == Exception 68 | raise Assertion.new(msg, caller) if act 69 | else 70 | yes = (test == act) 71 | msg = "#{exp}.equate? #{act}" unless msg 72 | raise Assertion.new(msg, caller) if yes 73 | end 74 | elsif test != Exception 75 | msg = "failed assertion (no message given)" unless msg 76 | raise Assertion.new(msg, caller) if test 77 | else 78 | return Expectation.new(self, :negate=>true, :backtrace=>caller) 79 | end 80 | 81 | if test 82 | msg = "failed assertion (no message given)" unless msg 83 | raise Assertion.new(msg, caller) if test 84 | else 85 | return Expectation.new(self, :negate=>true, :backtrace=>caller) 86 | end 87 | end 88 | 89 | # Same as #assert!. 90 | # 91 | # 4.refute == 4 #=> Assertion Error 92 | # 93 | alias_method :refute, :assert! 94 | 95 | end 96 | 97 | end 98 | 99 | class ::Object #:nodoc: 100 | include Grammar::Assert 101 | end 102 | 103 | end 104 | 105 | -------------------------------------------------------------------------------- /lib/qed/utils.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # Glob pattern used to search for project's root directory. 4 | ROOT_PATTERN = '{.ruby*,.git/,.hg/,_darcs/}' 5 | 6 | # Glob pattern for standard config file. 7 | CONFIG_PATTERN = '{.qed,etc/qed.rb,config/qed.rb,Qedfile}' 8 | 9 | # Home directory. 10 | HOME = File.expand_path('~') 11 | 12 | # 13 | module Utils 14 | extend self 15 | 16 | # 17 | def load_config 18 | load_etc unless ENV['noetc'] 19 | load_rc unless ENV['norc'] 20 | end 21 | 22 | # 23 | def load_rc 24 | rc_file = File.join(root, '.rubyrc') 25 | if File.exist?(rc_file) 26 | begin 27 | require 'rc/api' 28 | RC.profile_switch 'qed', '-p', '--profile' 29 | RC.configure 'qed' do |config| 30 | QED.configure(config.profile, &config) 31 | end 32 | rescue LoadError 33 | end 34 | end 35 | end 36 | 37 | # 38 | def load_etc 39 | file = Dir.glob(File.join(root, CONFIG_PATTERN)).first 40 | if file 41 | load file 42 | end 43 | end 44 | 45 | # 46 | def root 47 | @root ||= find_root 48 | end 49 | 50 | # TODO: find a way to not need $ROOT global. 51 | 52 | # 53 | # Locate project's root directory. This is done by searching upward 54 | # in the file heirarchy for the existence of one of the following: 55 | # 56 | # .ruby 57 | # .git/ 58 | # .hg/ 59 | # _darcs/ 60 | # .qed 61 | # .qed.rb 62 | # qed.rb 63 | # 64 | # Failing to find any of these locations, resort to the fallback: 65 | # 66 | # lib/ 67 | # 68 | # If that isn't found, then returns a temporary system location. 69 | # and sets `rootless` to true. 70 | # 71 | def find_root(path=nil) 72 | return ($ROOT = system_tmpdir) if @rootless 73 | 74 | path = File.expand_path(path || Dir.pwd) 75 | path = File.dirname(path) unless File.directory?(path) 76 | 77 | root = lookup(ROOT_PATTERN, path) || lookup(CONFIG_PATTERN, path) 78 | return root if root 79 | 80 | #root = lookup(path, '{qed,demo,spec}/') 81 | #return root if root 82 | 83 | root = lookup('lib/', path) 84 | 85 | if !root 86 | warn "QED is running rootless." 87 | system_tmpdir 88 | @rootless = true 89 | else 90 | root 91 | end 92 | 93 | $ROOT = root 94 | 95 | #abort "QED failed to resolve project's root location.\n" + 96 | # "QED looks for following entries to identify the root:\n" + 97 | # ROOT_PATTERN + 98 | # "Please add one of them to your project to proceed." 99 | end 100 | 101 | # 102 | # Lookup path +glob+, searching each higher directory 103 | # in turn until just before the users home directory 104 | # is reached or just before the system's root directory. 105 | # 106 | def lookup(glob, path=Dir.pwd) 107 | until path == HOME or path == '/' # until home or root 108 | mark = Dir.glob(File.join(path,glob), File::FNM_CASEFOLD).first 109 | return path if mark 110 | path = File.dirname(path) 111 | end 112 | end 113 | 114 | # 115 | # System-wide temporary directory for QED executation. 116 | # 117 | def system_tmpdir 118 | @system_tmpdir ||= ( 119 | File.join(Dir.tmpdir, 'qed', File.basename(Dir.pwd), Time.new.strftime("%Y%m%d%H%M%S")) 120 | ) 121 | end 122 | 123 | end 124 | 125 | end 126 | -------------------------------------------------------------------------------- /lib/qed/demo.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | require 'qed/core_ext' 7 | require 'qed/parser' 8 | require 'qed/evaluator' 9 | require 'qed/applique' 10 | 11 | # The Demo class ecapsulates a demonstrandum script. 12 | # 13 | class Demo 14 | 15 | # Demonstrandum file. 16 | attr :file 17 | 18 | # Parser mode. 19 | attr :mode 20 | 21 | # Steup new Demo instance. 22 | # 23 | # @param [String] file 24 | # Path to demo file. 25 | # 26 | # @param [Hash] options 27 | # 28 | # @option options [Symbol] :mode 29 | # Either `:comment` or other for normal mode. 30 | # 31 | # @option options [Strng] :at 32 | # Working directory. 33 | # 34 | # @option options [Array] :applique 35 | # Overriding applique. Used to import demos into other demos safely. 36 | # 37 | # @option options [Scope] :scope 38 | # Overriding scope, otherwise new Scope instance is created. 39 | # 40 | def initialize(file, options={}) 41 | @file = file 42 | 43 | @mode = options[:mode] 44 | @applique = options[:applique] 45 | end 46 | 47 | # Expanded dirname of +file+. 48 | def directory 49 | @directory ||= File.expand_path(File.dirname(file)) 50 | end 51 | 52 | # File basename less extension. 53 | def name 54 | @name ||= File.basename(file).chomp(File.extname(file)) 55 | end 56 | 57 | # Returns a cached Array of Applique modules. 58 | def applique 59 | @applique ||= ( 60 | list = [Applique.new] 61 | applique_locations.each do |location| 62 | #Dir[location + '/**/*'].each do |file| 63 | Dir[location + '/*'].each do |file| 64 | next if File.directory?(file) 65 | list << Applique.for(file) 66 | end 67 | end 68 | list 69 | ) 70 | end 71 | 72 | # 73 | def applique_prime 74 | applique.first 75 | end 76 | 77 | # Returns a list of applique directories to be used by this 78 | # demonstrastion. 79 | def applique_locations 80 | @applique_locations ||= ( 81 | locations = [] 82 | dirpath = Pathname.new(File.dirname(file)) 83 | dirpath.ascend do |path| 84 | break if path == Dir.pwd 85 | dir = File.join(path, 'applique') 86 | if File.directory?(dir) 87 | locations << dir 88 | end 89 | end 90 | locations 91 | ) 92 | end 93 | 94 | # Demo steps, cached from parsing. 95 | # 96 | # @return [Array] parsed steps 97 | def steps 98 | @steps ||= parser.parse 99 | end 100 | 101 | # Parse and cache demonstration script. 102 | # 103 | # @return [Array] list of steps (abstract syntax tree) 104 | alias_method :parse, :steps 105 | 106 | # Get a new Parser instance for this demo. 107 | # 108 | # @return [Parser] parser for this demo 109 | def parser 110 | Parser.new(self, :mode=>mode) 111 | end 112 | 113 | # Run demo through {Evaluator} instance with given observers. 114 | def run(options={}) 115 | Evaluator.run(self, options) 116 | end 117 | 118 | # 119 | #def source 120 | # @source ||= ( 121 | # #case file 122 | # #when /^http/ 123 | # # ext = File.extname(file).sub('.','') 124 | # # open(file) 125 | # #else 126 | # File.read(file) 127 | # #end 128 | # ) 129 | #end 130 | 131 | end 132 | 133 | end 134 | -------------------------------------------------------------------------------- /lib/qed/reporter/verbatim.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | module Reporter #:nodoc: 3 | 4 | require 'qed/reporter/abstract' 5 | 6 | # = Verbose ANSI Console Reporter 7 | # 8 | class Verbatim < Abstract 9 | 10 | # 11 | def before_session(session) 12 | @start_time = Time.now 13 | 14 | trap "INFO" do 15 | print_time 16 | print_tally 17 | end if INFO_SIGNAL 18 | end 19 | 20 | def step(step) 21 | @_explain = step.explain.dup 22 | end 23 | 24 | # 25 | def match(step, md) 26 | unless md[0].empty? 27 | @_explain.sub!(md[0], md[0].ansi(:bold)) 28 | end 29 | end 30 | 31 | # 32 | def applique(step) 33 | io.print "#{@_explain}".ansi(:cyan) 34 | io.print "#{step.example}" #.ansi(:blue) 35 | end 36 | 37 | # 38 | def pass(step) 39 | super(step) 40 | if step.heading? 41 | if step.code? 42 | io.print "#{@_explain}".ansi(:bold, :cyan) 43 | else 44 | io.print "#{@_explain}".ansi(:bold) 45 | end 46 | else 47 | io.print "#{@_explain}".ansi(:cyan) 48 | end 49 | 50 | if step.has_example? 51 | if step.data? 52 | io.print "#{step.example}" #.ansi(:magenta) 53 | else 54 | io.print "#{step.example}".ansi(:green) 55 | end 56 | end 57 | end 58 | 59 | # 60 | def fail(step, error) 61 | super(step, error) 62 | 63 | tab = step.text.index(/\S/) 64 | 65 | if step.heading? 66 | if step.code? 67 | io.print "#{@_explain}".ansi(:bold, :magenta) 68 | else 69 | io.print "#{@_explain}".ansi(:bold) 70 | end 71 | else 72 | io.print "#{@_explain}".ansi(:magenta) 73 | end 74 | 75 | if step.has_example? 76 | if step.data? 77 | io.print "#{step.example}".ansi(:red) 78 | else 79 | io.print "#{step.example}".ansi(:red) 80 | end 81 | end 82 | 83 | msg = [] 84 | msg << "FAIL: ".ansi(:bold, :red) + error.message.to_s #to_str 85 | msg << sane_backtrace(error).join("\n").ansi(:bold) 86 | msg = msg.join("\n") 87 | 88 | io.puts msg.tabto(tab||2) 89 | io.puts 90 | end 91 | 92 | # 93 | def error(step, error) 94 | super(step, error) 95 | 96 | raise error if $DEBUG # TODO: Should this be here? 97 | 98 | tab = step.text.index(/\S/) 99 | 100 | if step.heading? 101 | if step.code? 102 | io.print "#{@_explain}".ansi(:bold, :magenta) 103 | else 104 | io.print "#{@_explain}".ansi(:bold) 105 | end 106 | else 107 | io.print "#{@_explain}".ansi(:magenta) 108 | end 109 | 110 | if step.has_example? 111 | if step.data? 112 | io.print "#{step.example}".ansi(:red) 113 | else 114 | io.print "#{step.example}".ansi(:red) 115 | end 116 | end 117 | 118 | msg = [] 119 | msg << "ERROR: #{error.class} ".ansi(:bold,:red) + error.message #.sub(/for QED::Context.*?$/,'') 120 | msg << sane_backtrace(error).join("\n").ansi(:bold) 121 | msg = msg.join("\n") #.ansi(:red) 122 | 123 | io.puts msg.tabto(tab||2) 124 | io.puts 125 | end 126 | 127 | # 128 | #def macro(step) 129 | # #io.puts 130 | # #io.puts step.text 131 | # io.print "#{step}".ansi(:magenta) 132 | # #io.puts 133 | #end 134 | 135 | # 136 | def after_session(session) 137 | trap 'INFO', 'DEFAULT' if INFO_SIGNAL 138 | print_time 139 | print_tally 140 | end 141 | 142 | end 143 | 144 | end #module Reporter 145 | end #module QED 146 | -------------------------------------------------------------------------------- /work/trash/grammar/expect.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | require 'qed/expectation' 4 | 5 | module Grammar 6 | 7 | # = Expect Nomenclature 8 | # 9 | # Provides expect nomenclature. This is Quarry's "standard" 10 | # nomenclature. 11 | # 12 | module Expect 13 | 14 | # The +expect+ method is a powerful tool for defining 15 | # expectations in your specifications. 16 | # 17 | # Like #should it can be used to designate an expectation 18 | # via a *functor*. 19 | # 20 | # 4.expect == 3 21 | # 22 | # Or it can be used in block form. 23 | # 24 | # expect(4){ 3 } 25 | # 26 | # This compares the expected value and the actual 27 | # value with broad equality. This is similar to 28 | # case equality (#===) but also checks other forms of 29 | # equality. See the #equate method. 30 | # 31 | # Of particluar utility is that #expect allows one to 32 | # specify if the block raises the error. 33 | # 34 | # expect NoMethodError do 35 | # not_a_method 36 | # end 37 | # 38 | def expect(exp=Expectation, &block) 39 | if exp == Expectation 40 | Expectation.new(self, :backtrace=>caller) 41 | elsif Exception >= exp 42 | begin 43 | act = block.call 44 | test = exp.equate?(act) 45 | msg = "#{exp}.equate? #{act}" 46 | rescue exp => error 47 | test = true 48 | #msg = "#{exp} expected to be raised" 49 | rescue Exception => error 50 | test = false 51 | msg = "#{exp} expected but #{error.class} was raised" 52 | end 53 | raise Assertion.new(msg, caller) unless test 54 | else 55 | act = block.call 56 | test = exp.equate?(act) 57 | msg = "#{exp}.equate? #{act}" 58 | raise Assertion.new(msg, caller) unless test 59 | end 60 | end 61 | 62 | # Designate a negated expectation. Read this as 63 | # "expect not". 64 | # 65 | # 4.expect! == 4 #=> Expectation Error 66 | # 67 | # See #expect. 68 | # 69 | # Note that this method would not be necessary if 70 | # Ruby would allow +!=+ to be defined as a method, 71 | # or perhaps +!+ as a unary method. 72 | # 73 | def expect!(exp=Expectation, &block) 74 | if exp == Expectation 75 | Expectation.new(self, :negate=>true, :backtrace=>caller) 76 | elsif Exception >= exp 77 | begin 78 | act = block.call 79 | test = !exp.equate?(act) 80 | msg = "! #{exp}.equate? #{act}" 81 | rescue exp => error 82 | test = false 83 | msg = "#{exp} raised" 84 | rescue Exception => error 85 | test = true 86 | #msg = "#{exp} expected but was #{error.class}" 87 | end 88 | raise Assertion.new(msg, caller) unless test 89 | else 90 | act = block.call 91 | test = !exp.equate?(act) 92 | msg = "! #{exp}.equate? #{act}" 93 | raise Assertion.new(msg, caller) unless test 94 | end 95 | end 96 | 97 | # See #expect! method. 98 | # 99 | alias_method :expect_not, :expect! 100 | 101 | end 102 | 103 | end 104 | 105 | class ::Object #:nodoc: 106 | include Grammar::Expect 107 | end 108 | 109 | module ::Kernel 110 | # Broad equality. 111 | # 112 | def equate?(actual) 113 | self.equal?(actual) || 114 | self.eql?(actual) || 115 | self == actual || 116 | self === actual 117 | end 118 | end 119 | 120 | end 121 | 122 | -------------------------------------------------------------------------------- /lib/qed/parser.rb: -------------------------------------------------------------------------------- 1 | require 'qed/step' 2 | 3 | module QED 4 | 5 | # The parser breaks down a demonstandum into structured object 6 | # to passed thru the script evaluator. 7 | # 8 | # Technically is defines it's own markup language but for 9 | # interoperability sake it is RDoc and/or Markdown. 10 | # 11 | class Parser 12 | 13 | # Setup new parser instance. 14 | # 15 | # @param [Demo] demo 16 | # This demo, which is to be parsed. 17 | # 18 | # @param [Hash] options 19 | # Parsing options. 20 | # 21 | # @option options [Symbol] :mode 22 | # Parse in `:comment` mode or default mode. 23 | # 24 | def initialize(demo, options={}) 25 | @demo = demo 26 | @mode = options[:mode] 27 | @steps = [] 28 | end 29 | 30 | # The demo to parse. 31 | attr :demo 32 | 33 | # Parser mode. 34 | attr :mode 35 | 36 | # Abstract Syntax Tree 37 | attr :steps 38 | 39 | # The demo's file to parse. 40 | def file 41 | demo.file 42 | end 43 | 44 | # Lines of demo, prepared for parsing into steps. 45 | def lines 46 | @lines ||= parse_lines 47 | end 48 | 49 | # Prepare lines for parsing into steps. 50 | def parse_lines 51 | case mode 52 | when :comment 53 | parse_comment_lines 54 | else 55 | index = 0 #-1 56 | File.readlines(file).to_a.map do |line| 57 | [index += 1, line] 58 | end 59 | end 60 | end 61 | 62 | # TODO: It would be nice if we could get ther require statement for the 63 | # comment mode to be relative to an actual loadpath. 64 | 65 | # Parse comment lines into a format that the parse method can use. 66 | def parse_comment_lines 67 | ruby_omit = false 68 | rdoc_omit = false 69 | lines = [ 70 | [0, "Load #{File.basename(file)} script.\n"], 71 | [0, "\n"], 72 | [0, " require '#{file}'\n"] 73 | ] 74 | index = 1 75 | File.readlines(file).each do |l| 76 | case l 77 | when /^=begin(?!\s+qed)/ 78 | ruby_omit = true 79 | when /^=end/ 80 | ruby_omit = false 81 | when /^\s*\#\-\-\s*$/ 82 | rdoc_omit = true 83 | when /^\s*\#\+\+\s*$/ 84 | rdoc_omit = false 85 | ##when /^\s*\#\ \-\-/ # not needed just double comment 86 | ## # -- skip internal comments 87 | when /^\s*##/ 88 | ## skip internal comments 89 | when /^\s*\#/ 90 | lines << [index, l.lstrip.sub(/^\#\ ?/, '')] unless (ruby_omit or rdoc_omit) 91 | else 92 | lines << [index, "\n"] unless lines.last[1] == "\n" unless (ruby_omit or rdoc_omit) 93 | end 94 | index += 1 95 | end 96 | lines 97 | end 98 | 99 | # Parse demo file into steps. 100 | def parse 101 | steps = [] 102 | blank = false 103 | indented = false 104 | explain = [] 105 | example = [] #Step.new(file) 106 | 107 | lines.each do |lineno, line| 108 | case line 109 | when /^\s*$/ # blank line 110 | blank = true 111 | if indented 112 | example << [lineno, line] 113 | else 114 | explain << [lineno, line] 115 | end 116 | when /\A\s+/ #/\A(\t|\ \ +)/ # indented 117 | indented = true 118 | blank = false 119 | example << [lineno, line] 120 | else 121 | if indented or blank 122 | steps << Step.new(demo, explain, example, steps.last) 123 | explain, example = [], [] #Step.new(file) 124 | end 125 | indented = false 126 | blank = false 127 | explain << [lineno, line] 128 | end 129 | end 130 | steps << Step.new(demo, explain, example, steps.last) 131 | @steps = steps 132 | end 133 | 134 | end 135 | 136 | end 137 | -------------------------------------------------------------------------------- /lib/qed/reporter/html.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | module QED 4 | module Reporter 5 | 6 | require 'qed/reporter/abstract' 7 | 8 | # = Html Reporter 9 | # 10 | # NOTE: This must be completely redesigned since we moved back 11 | # to text based evaluation --which makes generting HTML with 12 | # modifications from the evaluation tricky. But I've come up 13 | # with a farily clever way to handle this. Take the original 14 | # and use Tilt to translate it into HTML, then take the 15 | # evaluation results for code steps and use it to search 16 | # the HTML for "the closest match". Find the \
 tag
 17 |   # associated with the text and add class and color style.
 18 |   # Of course the tricky part is the matching, but if we
 19 |   # run the text snippet through Tilt as well we should be
 20 |   # able to get an exact match. It won't be fast, but it should
 21 |   # work.
 22 | 
 23 |   class Html < Abstract
 24 | 
 25 |     #
 26 |     def initialize(*args)
 27 |       require 'erb'
 28 | 
 29 |       begin
 30 |         require 'rubygems'
 31 |         gem 'rdoc'
 32 |         require 'rdoc'
 33 |       rescue
 34 |       end
 35 | 
 36 |       super(*args)
 37 |     end
 38 | 
 39 |     #
 40 |     def before_session(session)
 41 |       io.puts <<-END
 42 |         
 43 |         
 44 |            
 45 |           QED Report
 46 |           
 56 |         
 57 |         
 58 |       END
 59 |     end
 60 | 
 61 |     #
 62 |     def before_demo(demo)
 63 |       io.puts <<-END
 64 |         

#{localize_file(demo.file)}

65 | END 66 | end 67 | 68 | def step(step) 69 | @_explain = step.explain.dup 70 | end 71 | 72 | # 73 | def match(step, md) 74 | #@match = md 75 | unless md[0].empty? 76 | @_explain.sub!(md[0], "#{md[0]}") 77 | end 78 | end 79 | 80 | # 81 | def pass(step) 82 | io.puts <<-END 83 |
84 | #{render(@_explain)} 85 | 86 |
#{step.example}
87 |
88 | END 89 | end 90 | 91 | # 92 | def fail(step, assertion) 93 | io.puts ERB.new(<<-END).result(binding) 94 |
95 | #{render(@_explain)} 96 | 97 |
#{step.example}
98 | 99 |
100 |

#{assertion.class} - #{assertion.message}

101 |
    102 | <% assertion.backtrace.each do |bt| %> 103 |
  1. <%= bt %>
  2. 104 | <% end %> 105 |
106 |
107 |
108 | END 109 | end 110 | 111 | # 112 | def error(step, exception) 113 | io.puts ERB.new(<<-END).result(binding) 114 |
115 | #{render(@_explain)} 116 | 117 |
#{step.example}
118 | 119 |
120 |

#{exception.class} - #{exception.message}

121 |
    122 | <% exception.backtrace.each do |bt| %> 123 |
  1. <%= bt %>
  2. 124 | <% end %> 125 |
126 |
127 |
128 | END 129 | end 130 | 131 | # 132 | def after_demo(demo) 133 | end 134 | 135 | # 136 | def after_session(session) 137 | io.puts <<-END 138 | 139 | 140 | END 141 | end 142 | 143 | private 144 | 145 | def render(str) 146 | rdoc.convert(str.strip) 147 | end 148 | 149 | def rdoc 150 | @rdoc ||= RDoc::Markup::ToHtml.new 151 | end 152 | 153 | #def h(str) 154 | # ERB::Util.html_escape(str) 155 | #end 156 | end 157 | 158 | end#module Reporter 159 | end#module QED 160 | 161 | -------------------------------------------------------------------------------- /work/consider/extract.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | raise "not ready yet" 4 | 5 | module QED 6 | 7 | # Extractor is a tool for extracting code from embedded 8 | # comment blocks. 9 | # 10 | # TODO: 11 | # - Should extract_block handle more than the first matching block? 12 | # - How can we handle embedded code in standard comments? Eg. # 13 | # 14 | module Extract 15 | 16 | extend self 17 | 18 | # Extract unit tests. This task scans every package script 19 | # looking for sections of the form: 20 | # 21 | # =begin test 22 | # ... 23 | # =end 24 | # 25 | # With appropriate headers, it copies these sections to files 26 | # in your project's test/ dir, which then can be run using the 27 | # Ratchet test task. The exact directory layout of the files to 28 | # be tested is reflected in the test directory. You can then 29 | # use project.rb's test task to run the tests. 30 | # 31 | # files Files to extract ['lib/**/*.rb'] 32 | # output Test directory ['test/'] 33 | # 34 | 35 | def test_extract(files=nil) 36 | output = 'test/embedded' # Don't think output should be setable. 37 | 38 | files = files || 'lib/**/*.rb' 39 | files = 'lib/**/*.rb' if TrueClass == files 40 | files = [files].flatten.compact 41 | 42 | filelist = files.collect{ |f| Dir.glob(f) } 43 | filelist.flatten! 44 | if filelist.empty? 45 | puts "No scripts found from which to extract tests." 46 | return 47 | end 48 | 49 | FileUtils.mkdir_p(output) unless File.directory?(output) 50 | 51 | #vrunner = VerbosityRunner.new("Extracting", verbosity?) 52 | #vrunner.setup(filelist.size) 53 | 54 | filelist.each do |file| 55 | #vrunner.prepare(file) 56 | 57 | testing = extract_test_from_file( file ) 58 | if testing.strip.empty? 59 | status = "[NONE]" 60 | else 61 | complete_test = create_test(testing, file) 62 | libpath = File.dirname(file) 63 | testfile = "test_" + File.basename(file) 64 | fp = File.join(output, libpath, testfile) 65 | unless File.directory?( File.dirname(fp)) 66 | FileUtils.mkdir_p(File.dirname(fp)) 67 | end 68 | File.open(fp, "w"){ |fw| fw << complete_test } 69 | status = "[TEST]" 70 | end 71 | 72 | #vrunner.complete(file, status) 73 | end 74 | 75 | #vrunner.finish( 76 | # :normal => "#{filelist.size} files had tests extracted.", 77 | # :check => false 78 | #) 79 | end 80 | 81 | private 82 | 83 | # Extract test from a file's testing comments. 84 | 85 | def extract_test_from_file(file) 86 | return nil if ! File.file?(file) 87 | tests = ""; inside = false 88 | fstr = File.read(file) 89 | fstr.split(/\n/).each do |l| 90 | if l =~ /^=begin[ ]*test/i 91 | tests << "\n" 92 | inside = true 93 | next 94 | elsif inside and l =~ /^=[ ]*end/ 95 | inside = false 96 | next 97 | end 98 | if inside 99 | tests << l << "\n" 100 | end 101 | end 102 | tests 103 | end 104 | 105 | # Generate the test. 106 | 107 | def create_test(testing, file) 108 | fp = file.split(/[\/]/) 109 | if fp[0] == 'lib' 110 | reqf = "require '#{fp[1..-1].join('/')}'" 111 | else 112 | reqf = '' 113 | end 114 | teststr = [] 115 | teststr << "# _____ _" 116 | teststr << "# |_ _|__ ___| |_" 117 | teststr << "# | |/ _ \\/ __| __|" 118 | teststr << "# | | __/\\__ \\ |_" 119 | teststr << "# |_|\\___||___/\\__|" 120 | teststr << "#" 121 | teststr << "# for #{file}" 122 | teststr << "#" 123 | teststr << "# Extracted #{Time.now}" 124 | teststr << "# Project.rb Test Extraction" 125 | teststr << "#" 126 | teststr << "" 127 | teststr << "#{reqf}" 128 | teststr << "" 129 | teststr << testing 130 | teststr << "" 131 | teststr.join("\n") 132 | end 133 | 134 | end #module Extract 135 | 136 | end #module Quarry 137 | 138 | -------------------------------------------------------------------------------- /lib/qed/applique.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # Applique is a module built per-script from the +applique+ directory. 4 | # Applique scripts are loaded at the start of a session. 5 | # 6 | # *The Applique* is the whole collection of appliques that apply to given 7 | # demonstrandum. The applique that apply are the scripts located in the 8 | # directory relative to the demonstrandum script and all such directories 9 | # above this upto and the project's root directory. 10 | # 11 | # All scripts in the Applique must be compatible/consistant. For two demos to 12 | # have separate applique they must be kept in separate directories. 13 | # 14 | class Applique < Module 15 | 16 | # Load cache. 17 | def self.cache 18 | @cache ||= {} 19 | end 20 | 21 | # TODO: may need to expand file to be absolute path 22 | 23 | # Create new Applique caching instance based-on +file+. 24 | # 25 | # @param [String] file 26 | # The file path to the applique file. 27 | # 28 | def self.for(file) 29 | cache[file] ||= new(file) 30 | end 31 | 32 | # Setup new Applique instance. 33 | # 34 | # @param [String] file 35 | # The file path to the applique file. 36 | # 37 | def initialize(file=nil) 38 | super() 39 | extend self 40 | 41 | @__matchers__ = [] 42 | @__signals__ = {} 43 | 44 | if file 45 | @file = file 46 | case File.extname(file) 47 | when '.rb' 48 | module_eval(File.read(file), file) 49 | else 50 | # little bit of a trick here, we create a new demo but manually 51 | # set the applique. That way the applique files won't be reloaded. 52 | # we then run the demo that applique get loaded. 53 | demo = Demo.new(file, :applique=>[self]) 54 | Evaluator.run(demo, :applique=>true) 55 | end 56 | end 57 | end 58 | 59 | # Duplicate matcher and signal advice when duplicting applique. 60 | # 61 | # @param [Applique] other 62 | # The original applique. 63 | # 64 | def initialize_copy(other) 65 | @__matchers__ = other.__matchers__.dup 66 | @__signals__ = other.__signals__.dup 67 | end 68 | 69 | # Array of matchers. 70 | attr :__matchers__ 71 | 72 | # Hash of signals. 73 | attr :__signals__ 74 | 75 | # Pattern matchers and "upon" events. 76 | # 77 | # @param [Symbol,Array] patterns 78 | # Event signal, or list of matches. 79 | # 80 | # @yield Procedure to run on event. 81 | def When(*patterns, &procedure) 82 | if patterns.size == 1 && Symbol === patterns.first 83 | type = "#{patterns.first}".to_sym 84 | @__signals__[type] = procedure 85 | #define_method(type, &procedure) 86 | else 87 | patterns = patterns.map do |p| 88 | if String === p 89 | p.split('...').map{ |e| e.strip } 90 | else 91 | p 92 | end 93 | end.flatten 94 | @__matchers__ << [patterns, procedure] 95 | end 96 | end 97 | 98 | # Before advice. 99 | # 100 | # @param [Symbol] type 101 | # Event signal (`:demo`). 102 | # 103 | # @yield Procedure to run on event. 104 | def Before(type=:demo, &procedure) 105 | type = type.to_sym 106 | type = :demo if type == :all 107 | type = :test if type == :each 108 | type = "before_#{type}".to_sym 109 | @__signals__[type] = procedure 110 | #define_method(type, &procedure) 111 | end 112 | 113 | # After advice. 114 | # 115 | # @param [Symbol] type 116 | # Event signal (`:demo`). 117 | # 118 | # @yield Procedure to run on event. 119 | def After(type=:demo, &procedure) 120 | type = type.to_sym 121 | type = :demo if type == :all 122 | type = :test if type == :each 123 | type = "after_#{type}".to_sym 124 | @__signals__[type] = procedure 125 | #define_method(type, &procedure) 126 | end 127 | 128 | # Code match-and-transform procedure. 129 | # 130 | # This is useful to transform human readable code examples 131 | # into proper exectuable code. For example, say you want to 132 | # run shell code, but want to make if look like typical 133 | # shell examples: 134 | # 135 | # $ cp fixture/a.rb fixture/b.rb 136 | # 137 | # You can use a transform to convert lines starting with '$' 138 | # into executable Ruby using #system. 139 | # 140 | # system('cp fixture/a.rb fixture/b.rb') 141 | # 142 | #def Transform(pattern=nil, &procedure) 143 | # 144 | #end 145 | 146 | # TODO: Clean backtrace when constant is not found ? 147 | 148 | # Redirect missing constants to Object classto simulate TOPLEVEL. 149 | # 150 | def const_missing(name) 151 | Object.const_get(name) 152 | end 153 | 154 | end 155 | 156 | end 157 | -------------------------------------------------------------------------------- /work/defunct/advice.rb: -------------------------------------------------------------------------------- 1 | raise "no needed any more" 2 | 3 | require 'qed/core_ext' 4 | 5 | module QED 6 | 7 | # = Advice 8 | # 9 | # This class tracks advice defined by demonstrandum and applique. 10 | # Advice are evaluated in Scope, so that they will have access 11 | # to the same local binding as the scripts themselves. 12 | # 13 | # There are two types of advice: *pattern matchers* 14 | # and *event signals*. 15 | # 16 | # == Pattern Matchers (When) 17 | # 18 | # Matchers are evaluated when they match a description. 19 | # 20 | # == Event Signals (Before, After) 21 | # 22 | # Event advice are triggered on symbolic targets which 23 | # represent an event in the evaluation process, such as 24 | # before an example is run, or after a demo finishes. 25 | # 26 | class Advice 27 | 28 | # 29 | attr :matchers 30 | 31 | # 32 | attr :signals 33 | 34 | # 35 | def initialize 36 | @matchers = [] 37 | @signals = [{}] 38 | end 39 | 40 | # 41 | #def initialize_copy(other) 42 | # @matchers = other.matchers.dup 43 | # @signals = other.signals.dup 44 | #end 45 | 46 | # 47 | def call(scope, type, *args) 48 | case type 49 | when :when 50 | call_matchers(scope, *args) 51 | else 52 | call_signals(scope, type, *args) 53 | end 54 | end 55 | 56 | # 57 | def add_event(type, &procedure) 58 | @signals.last[type.to_sym] = procedure 59 | end 60 | 61 | # 62 | def add_match(patterns, &procedure) 63 | @matchers << [patterns, procedure] 64 | end 65 | 66 | # React to an event. 67 | def call_signals(scope, type, *args) 68 | @signals.each do |set| 69 | proc = set[type.to_sym] 70 | #proc.call(*args) if proc 71 | scope.instance_exec(*args, &proc) if proc 72 | end 73 | end 74 | 75 | # 76 | def call_matchers(scope, section) 77 | match = section.text 78 | args = section.arguments 79 | matchers.each do |(patterns, proc)| 80 | compare = match 81 | matched = true 82 | params = [] 83 | patterns.each do |pattern| 84 | case pattern 85 | when Regexp 86 | regex = pattern 87 | else 88 | regex = match_string_to_regexp(pattern) 89 | end 90 | if md = regex.match(compare) 91 | params.concat(md[1..-1]) 92 | compare = md.post_match 93 | else 94 | matched = false 95 | break 96 | end 97 | end 98 | if matched 99 | params += args 100 | #proc.call(*params) 101 | scope.instance_exec(*params, &proc) 102 | end 103 | end 104 | end 105 | 106 | # Clear last set of advice. 107 | def signals_reset 108 | @signals.pop 109 | end 110 | 111 | # 112 | def signals_setup 113 | @signals.push({}) 114 | end 115 | 116 | # Clear advice. 117 | def signals_clear(type=nil) 118 | if type 119 | @signals.each{ |set| set.delete(type.to_sym) } 120 | else 121 | @signals = [{}] 122 | end 123 | end 124 | 125 | private 126 | 127 | PATTERN = /((\(\(.*?\)\))(?!\))|([#$]\(.*?\)))/ 128 | 129 | # Convert matching string into a regular expression. If the string 130 | # contains double parenthesis, such as ((.*?)), then the text within 131 | # them is treated as in regular expression and kept verbatium. 132 | # 133 | # TODO: Better way to isolate regexp. Maybe ?:(.*?) or /(.*?)/. 134 | # 135 | # TODO: Now that we can use multi-patterns, do we still need this? 136 | # 137 | def match_string_to_regexp(str) 138 | #str = str.split(/(\(\(.*?\)\))(?!\))/).map{ |x| 139 | # x =~ /\A\(\((.*)\)\)\Z/ ? $1 : Regexp.escape(x) 140 | #}.join 141 | #str = str.gsub(/\\\s+/, '\s+') 142 | #Regexp.new(str, Regexp::IGNORECASE) 143 | 144 | #str = str.split(/([#$]\(.*?\))/).map{ |x| 145 | # x =~ /\A[#$]\((.*)\)\Z/ ? ($1.start_with?('#') ? "(#{$1})" : $1 ) : Regexp.escape(x) 146 | #}.join 147 | #str = str.gsub(/\\\s+/, '\s+') 148 | #Regexp.new(str, Regexp::IGNORECASE) 149 | 150 | $stderr.puts "HERE!!!!!!" 151 | 152 | str = str.split(PATTERN).map{ |x| 153 | case x 154 | when /\A\(\((.*)\)\)\Z/ 155 | $1 156 | when /\A[#$]\((.*)\)\Z/ 157 | $1.start_with?('#') ? "(#{$1})" : $1 158 | else 159 | Regexp.escape(x) 160 | end 161 | }.join 162 | 163 | str = str.gsub(/\\\s+/, '\s+') 164 | 165 | Regexp.new(str, Regexp::IGNORECASE) 166 | 167 | #rexps = [] 168 | #str = str.gsub(/\(\((.*?)\)\)/) do |m| 169 | # rexps << '(' + $1 + ')' 170 | # "\0" 171 | #end 172 | #str = Regexp.escape(str) 173 | #rexps.each do |r| 174 | # str = str.sub("\0", r) 175 | #end 176 | #str = str.gsub(/(\\\ )+/, '\s+') 177 | #Regexp.new(str, Regexp::IGNORECASE) 178 | end 179 | 180 | end 181 | 182 | end 183 | 184 | -------------------------------------------------------------------------------- /lib/qed/cli/qed.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | # 4 | def self.cli(*argv) 5 | Session.cli(*argv) 6 | end 7 | 8 | class Session 9 | 10 | # 11 | # Session settings are passed to `Session.new`. 12 | # 13 | #def self.settings 14 | # @settings ||= Settings.new 15 | #end 16 | 17 | # 18 | # Command line interface for running demos. 19 | # 20 | # When running `qed` on the command line tool, QED can use 21 | # either a automatic configuration file, via the RC library, 22 | # or setup configuration via an explicitly required file. 23 | # 24 | # Using a master configuraiton file, add a `config :qed` entry. 25 | # For example: 26 | # 27 | # config :qed, :profile=>:simplecov do 28 | # require 'simplecov' 29 | # SimpleCov.start do 30 | # coverage_dir 'log/coverage' 31 | # end 32 | # end 33 | # 34 | # To not use RC, just create a requirable file such as `config/qed-coverage.rb` 35 | # 36 | # QED.configure do |qed| 37 | # require 'simplecov' 38 | # SimpleCov.start do 39 | # coverage_dir 'log/coverage' 40 | # end 41 | # end 42 | # 43 | # Then when running qed use: 44 | # 45 | # $ qed -r ./config/qed-coverage.rb 46 | # 47 | def self.cli(*argv) 48 | require 'optparse' 49 | require 'shellwords' 50 | 51 | # we are loading this here instead of above so simplecov coverage works better 52 | # (actually, this is really not a problem anymore, but we'll leave it for now) 53 | require 'qed/session' 54 | 55 | Utils.load_config 56 | 57 | options = cli_parse(argv) 58 | 59 | settings = Settings.new(options) 60 | session = Session.new(settings) 61 | success = session.run 62 | 63 | exit -1 unless success 64 | end 65 | 66 | # 67 | # Build an instance of OptionParser. 68 | # 69 | def self.cli_parse(argv) 70 | options = {} 71 | 72 | parser = OptionParser.new do |opt| 73 | opt.banner = "Usage: qed [options] " 74 | 75 | opt.separator("Report Formats (pick one):") 76 | 77 | #opt.on('--dotprogress', '-d', "use dot-progress reporter [default]") do 78 | # options[:format] = :dotprogress 79 | #end 80 | opt.on('--verbatim', '-v', "shortcut for verbatim reporter") do 81 | options[:format] = :verbatim 82 | end 83 | opt.on('--tapy', '-y', "shortcut for TAP-Y reporter") do 84 | options[:format] = :tapy 85 | end 86 | #opt.on('--bullet', '-b', "use bullet-point reporter") do 87 | # options[:format] = :bullet 88 | #end 89 | #opt.on('--html', '-h', "use underlying HTML reporter") do 90 | # options[:format] = :html 91 | #end 92 | #opt.on('--script', "psuedo-reporter") do 93 | # options[:format] = :script # psuedo-reporter 94 | #end 95 | opt.on('--format', '-f FORMAT', "use custom reporter") do |format| 96 | options[:format] = format.to_sym 97 | end 98 | 99 | opt.separator("Control Options:") 100 | 101 | opt.on('-p', '--profile NAME', "load runtime profile") do |name| 102 | options[:profile] = name 103 | end 104 | opt.on('--comment', '-c', "run comment code") do 105 | options[:mode] = :comment 106 | end 107 | opt.on('--loadpath', "-I PATH", "add paths to $LOAD_PATH") do |paths| 108 | options[:loadpath] = paths.split(/[:;]/).map{|d| File.expand_path(d)} 109 | end 110 | opt.on('--require', "-r LIB", "require feature (immediately)") do |paths| 111 | requires = paths.split(/[:;]/) 112 | requires.each do |file| 113 | require file 114 | end 115 | end 116 | opt.on('--rooted', '-R', "run from project root instead of temporary directory") do 117 | options[:rooted] = true 118 | end 119 | opt.on('--trace', '-t [COUNT]', "number of backtraces for exceptions (0 for all)") do |cnt| 120 | #options[:trace] = true 121 | ENV['trace'] = cnt 122 | end 123 | opt.on('--warn', "run with warnings turned on") do 124 | $VERBOSE = true # wish this were called $WARN! 125 | end 126 | opt.on('--debug', "exit immediately upon raised exception") do 127 | $DEBUG = true 128 | end 129 | 130 | opt.separator("Optional Commands:") 131 | 132 | opt.on_tail('--version', "display version") do 133 | puts "QED #{QED::VERSION}" 134 | exit 135 | end 136 | opt.on_tail('--copyright', "display copyrights") do 137 | puts "Copyright (c) 2008 Thomas Sawyer, Apache 2.0 License" 138 | exit 139 | end 140 | opt.on_tail('--help', '-h', "display this help message") do 141 | puts opt 142 | 143 | unless settings.profiles.empty? 144 | puts "Available Profiles:" 145 | #require 'confection' 146 | QED.profiles.each do |name| 147 | next if name.strip == '' 148 | puts " -p #{name}" 149 | end 150 | end 151 | 152 | exit -1 153 | end 154 | end 155 | 156 | parser.parse!(argv) 157 | 158 | options[:files] = argv unless argv.empty? 159 | 160 | return options 161 | end 162 | 163 | end 164 | 165 | end 166 | -------------------------------------------------------------------------------- /demo/02_advice.md: -------------------------------------------------------------------------------- 1 | # Advice 2 | 3 | Advice are event-based procedures that augment demonstrations. 4 | They are used to keep demonstrations clean of extraneous, 5 | repetitive and merely adminstrative code that the reader does 6 | not need to see over and over. 7 | 8 | Typically you will want to put advice definitions in applique 9 | files, rather then place them directly in the demonstration 10 | document, but you can do so, as you will see in this document. 11 | 12 | ## Before and After 13 | 14 | QED supports *before* and *after* clauses in a specification 15 | through the use of Before and After code blocks. These blocks 16 | are executed at the beginning and at the end of each indicated 17 | step. 18 | 19 | We use a *before* clause if we want to setup some code at the 20 | start of each code step. 21 | 22 | a, z = nil, nil 23 | 24 | Before do 25 | a = "BEFORE" 26 | end 27 | 28 | And an *after* clause to teardown objects after a code step. 29 | 30 | After do 31 | z = "AFTER" 32 | end 33 | 34 | Notice we assigned +a+ and +z+ before the block. This was to ensure 35 | their visibility in the scope later. Now, lets verify that the *before* 36 | and *after* clauses work. 37 | 38 | a.assert == "BEFORE" 39 | 40 | a = "A" 41 | z = "Z" 42 | 43 | And now. 44 | 45 | z.assert == "AFTER" 46 | 47 | There can be more than one before and after clause at a time. If we 48 | define a new *before* or *after* clause later in the document, 49 | it will be appended to the current list of clauses in use. 50 | 51 | As a demonstration of this, 52 | 53 | b = nil 54 | 55 | Before do 56 | b = "BEFORE AGAIN" 57 | end 58 | 59 | We will see it is the case. 60 | 61 | b.assert == "BEFORE AGAIN" 62 | 63 | Only use *before* and *after* clauses when necessary --specifications 64 | are generally more readable without them. Indeed, some developers 65 | make a policy of avoiding them altogether. YMMV. 66 | 67 | ## Caveats of Before and After 68 | 69 | Instead of using Before and After clauses, it is wiser to 70 | define a reusable setup method. For example, in the helper 71 | if we define a method such as #prepare_example. 72 | 73 | def prepare_example 74 | "Hello, World!" 75 | end 76 | 77 | Then we can reuse it in later code blocks. 78 | 79 | example = prepare_example 80 | example.assert == "Hello, World!" 81 | 82 | The advantage to this is that it gives the reader an indication 83 | of what is going on behind the scenes, rather the having 84 | an object just magically appear. 85 | 86 | ## Event Targets 87 | 88 | There is a small set of advice targets that do not come before or after, 89 | rather they occur *upon* a particular event. These include +:pass+, +:fail+ 90 | and +:error+ for when a code block passes, fails or raises an error; and 91 | +:step+, +:applique+, +:match+ and +:test:+ which targets the processing 92 | of a demo step and it's example excecution. 93 | 94 | These event targets can be advised by calling the +When+ method 95 | with the target type as an argument along with the code block 96 | to be run when the event is triggered. 97 | 98 | x = [] 99 | 100 | When(:step) do |section| 101 | section.text.scan(/^\*(.*?)$/) do |m| 102 | x << $1.strip 103 | end 104 | end 105 | 106 | Now let's see if it worked. 107 | 108 | * SampleA 109 | * SampleB 110 | * SampleC 111 | 112 | So +x+ should now contain these three list samples. 113 | 114 | x.assert == [ 'SampleA', 'SampleB', 'SampleC' ] 115 | 116 | ## Pattern Matchers 117 | 118 | QED also supports comment match triggers. With the +When+ method one can 119 | define procedures to run when a given pattern matches comment text. 120 | 121 | When 'given the facts' do 122 | @facts = "this is truth" 123 | end 124 | 125 | Then whenever the words, 'given the facts' appear in step description 126 | the `@facts` varaible will be set. 127 | 128 | @facts.assert == "this is truth" 129 | 130 | Pattern matchers reall shine when we also add captures to the mix. 131 | 132 | When 'given a setting @a equal to (((\d+)))' do |n| 133 | @a = n.to_i 134 | end 135 | 136 | Now, @a will be set to 1 whenever a comment like this one contains, 137 | "given a setting @a equal to 1". 138 | 139 | @a.assert == 1 140 | 141 | A string pattern is translated into a regular expression. In fact, you can 142 | use a regular expression if you need more control over the match. When 143 | using a string all spaces are converted to \s+ and anything within 144 | double-parenthesis is treated as raw regular expression. Since the above 145 | example has (((\d+))), the actual regular expression contains (\d+), 146 | so any number can be used. For example, "given a setting @a equal to 2". 147 | 148 | @a.assert == 2 149 | 150 | When clauses can also use consecutive pattern matching. For instance 151 | we could write, 152 | 153 | When 'first match #(((\d+)))', 'then match #(((\d+)))' do |i1, i2| 154 | @a = [i1.to_i, i2.to_i] 155 | end 156 | 157 | So that 'first match #1' will be looked for first, and only after 158 | that if 'then match #2' is found, will it be considered a complete match. 159 | All regular expression slots are collected from all matches and passed to 160 | the block. We can see that the rule matched this very paragraph. 161 | 162 | @a.assert == [1,2] 163 | 164 | This concludes the basic overview of QED's specification system, which 165 | is itself a QED document. Yes, we eat our own dog food. 166 | 167 | -------------------------------------------------------------------------------- /lib/qed/settings.rb: -------------------------------------------------------------------------------- 1 | require 'qed/configure' 2 | require 'qed/utils' 3 | 4 | module QED 5 | 6 | # Settings ecapsulates setup configuration for running QED. 7 | # 8 | class Settings 9 | 10 | require 'tmpdir' 11 | require 'fileutils' 12 | 13 | # If files are not specified than these directories 14 | # will be searched. 15 | DEFAULT_FILES = ['qed', 'demo', 'spec'] 16 | 17 | # Directory names to omit from automatic selection. 18 | OMIT_PATHS = %w{applique helpers support sample samples fixture fixtures} 19 | 20 | # 21 | # Initialize new Settings instance. 22 | # 23 | def initialize(options={}, &block) 24 | initialize_defaults 25 | 26 | @profile = (options.delete(:profile) || default_profile).to_s 27 | 28 | load_profile(&block) 29 | 30 | options.each do |key, val| 31 | send("#{key}=", val) if val 32 | end 33 | end 34 | 35 | # 36 | # Initialize default settings. 37 | # 38 | def initialize_defaults 39 | @files = nil #DEFAULT_FILES 40 | @format = :dot 41 | @trace = false 42 | @mode = nil # ? 43 | @loadpath = ['lib'] 44 | @omit = OMIT_PATHS 45 | @rootless = false 46 | @requires = [] 47 | #@profile = :default 48 | end 49 | 50 | # Profile name can come from `profile` or `p` environment variables. 51 | def default_profile 52 | ENV['profile'] || ENV['p'] || 'default' 53 | end 54 | 55 | # Demonstration files (or globs). 56 | def files 57 | @files ||= ( 58 | [DEFAULT_FILES.find{ |d| File.directory?(d) }].compact 59 | ) 60 | end 61 | 62 | # 63 | def files=(files) 64 | @files = ( 65 | if files.nil? or files.empty? 66 | nil 67 | else 68 | Array(files).flatten.compact 69 | end 70 | ) 71 | end 72 | 73 | # File patterns to omit. 74 | attr_accessor :omit 75 | 76 | # Output format. 77 | attr_accessor :format 78 | 79 | # Trace execution? 80 | attr_accessor :trace 81 | 82 | # Parse mode. 83 | attr_accessor :mode 84 | 85 | # Paths to be added to $LOAD_PATH. 86 | attr_accessor :loadpath 87 | 88 | # Libraries to be required. 89 | attr_accessor :requires 90 | 91 | # Operate from project root? 92 | attr_accessor :rooted 93 | 94 | # Operate from system temporary directory? 95 | attr_accessor :rootless 96 | 97 | # 98 | # Load QED configuration profile. The load procedure is stored as 99 | # a Proc object in a hash b/c different configuration systems 100 | # can be used. 101 | # 102 | def load_profile(&block) 103 | config = QED.profiles[@profile] 104 | config.call(self) if config 105 | end 106 | 107 | # 108 | # Profiles are collected from the RC library, unless 109 | # RC is deactivated via the override file. 110 | # 111 | def profiles 112 | QED.profiles.keys 113 | end 114 | 115 | # 116 | # Operate relative to project root directory, or use system's location. 117 | # 118 | def rootless? 119 | @rootless 120 | end 121 | 122 | # 123 | # Project's root directory. 124 | # 125 | def root 126 | Utils.root 127 | end 128 | 129 | # 130 | # Alias for `#root`. 131 | # 132 | alias_method :root_directory, :root 133 | 134 | # 135 | # Temporary directory. If `#rootless?` return true then this will be 136 | # a system's temporary directory (e.g. `/tmp/qed/foo/20111117242323/`). 137 | # Otherwise, it will local to the project's root int `tmp/qed/`. 138 | # 139 | def temporary_directory 140 | @temporary_directory ||= ( 141 | if rootless? 142 | Utils.system_tmpdir 143 | else 144 | File.join(root_directory, 'tmp', 'qed') 145 | end 146 | #FileUtils.mkdir_p(dir) unless File.directory?(dir) 147 | ) 148 | end 149 | 150 | # 151 | # Shorthand for `#temporary_directory`. 152 | # 153 | alias_method :tmpdir, :temporary_directory 154 | 155 | # 156 | # Remove and recreate temporary working directory. 157 | # 158 | def clear_directory 159 | FileUtils.rm_r(tmpdir) if File.exist?(tmpdir) 160 | FileUtils.mkdir_p(tmpdir) 161 | end 162 | 163 | =begin 164 | # 165 | # Define a profile. 166 | # 167 | # @deprecated Confection library is used instead. 168 | # 169 | # @param [#to_s] name 170 | # Name of profile. 171 | # 172 | # @yield Procedure to run for profile. 173 | # 174 | # @return [Proc] The procedure. 175 | # 176 | def profile(name=nil, &block) 177 | return @profile unless name 178 | return @profile[name.to_s] unless block 179 | @profiles[name.to_s] = block 180 | end 181 | =end 182 | 183 | private 184 | 185 | # TODO: Support .map in future ? 186 | 187 | ## 188 | ## Return cached file map from a project's `.map` file, if it exists. 189 | ## 190 | #def file_map 191 | # @file_map ||= ( 192 | # if File.exist?(map_file) 193 | # YAML.load_file(map_file) 194 | # else 195 | # {} 196 | # end 197 | # ) 198 | #end 199 | 200 | ## 201 | ## Lookup, cache and return `.map` map file. 202 | ## 203 | #def map_file 204 | # @_map_file ||= File.join(root_directory,MAP_FILE) 205 | #end 206 | 207 | # 208 | #def load_confection_profile(name) 209 | # config = confection(:qed, name.to_sym) 210 | # config.exec 211 | #end 212 | 213 | end 214 | 215 | end 216 | 217 | -------------------------------------------------------------------------------- /work/defunct/doubles/spy.rb: -------------------------------------------------------------------------------- 1 | raise "Spy class is under construction" 2 | 3 | module QED 4 | 5 | # = Spy 6 | # 7 | # Spy (aka DuckHunter) is a decoy object which is dropped into 8 | # methods which records the calls made against it --hence a method probe. 9 | # Of course, it is not perfect --an inescapable matter it seems for any 10 | # internal probe. There are a couple of issues related to conditionals. 11 | # Since the method test for a certain condition against the decoy, how 12 | # is the decoy to respond? Thus ceratin paths in the code may never get 13 | # exceuted and thus go unmapped. If Ruby had better conditional reflection 14 | # (i.e. if 'if', 'case', 'unless', 'when', etc. were true methods) then 15 | # this could be fixed by making the Probe reentrant, mapping out variant 16 | # true/false/nil replies. The likely insurmountable problem though is the 17 | # Halting problem. A probe can cause some methods to complete execution. 18 | # It's pretty rare, but it can happen and little can be done about it (I think). 19 | # 20 | # Note, the alternative to this kind of probe is a program that examines, rather 21 | # then executes, the code. This would circumvent the above problems, but run 22 | # into difficulties with dynamic evals. It would also be more complicated, 23 | # but might prove a better means in the future. 24 | # 25 | # This script is provided for experimetnal purposes. Please inform the author 26 | # if you find ways to improve it or put it to an interesting use. 27 | # 28 | # == Synopsis 29 | # 30 | # require 'methodprobe' 31 | # 32 | # def amethod(x) 33 | # x + 1 34 | # end 35 | # 36 | # p method(:amethod).signiture 37 | # p method(:amethod).signiture(:class) 38 | # p method(:amethod).signiture(:pretty) 39 | # 40 | # produces 41 | # 42 | # [["+"]] 43 | # [{"+"=>[["Fixnum"]]}] 44 | # [["+( Fixnum )"]] 45 | # 46 | class Spy 47 | 48 | def self.duckcall 49 | begin 50 | yield 51 | rescue TypeError => e 52 | self.send(e.message) 53 | retry 54 | end 55 | end 56 | 57 | attr_reader :ducks, :decoys 58 | 59 | def initialize 60 | @ducks, @decoys = {}, {} 61 | end 62 | 63 | def initialize_copy(from) 64 | initialize 65 | end 66 | 67 | def method_missing(aSym, *args) 68 | aSymStr = aSym.to_s 69 | 70 | # This will happen the first time 71 | @ducks[aSymStr] ||= [] #unless @ducks[aSymStr] 72 | @ducks[aSymStr] << args.collect { |a| "#{a.class}" } 73 | 74 | decoy = self.dup 75 | 76 | @decoys[aSymStr] ||= [] #unless @decoys[aSymStr] 77 | @decoys[aSymStr] << decoy 78 | 79 | # build proxy? 80 | #begin 81 | # d = <<-HERE 82 | # def self.#{aSymStr}(*args) 83 | # # This will happen the subsequent times 84 | # @ducks["#{aSymStr}"] << args.collect { |a| #{'"#{a.class}"'} } 85 | # @ducks["#{aSymStr}"].uniq! 86 | # decoy = self.dup 87 | # @decoys["#{aSymStr}"] = [] unless @decoys["#{aSymStr}"] 88 | # @decoys["#{aSymStr}"] << decoy 89 | # decoy 90 | # end 91 | # HERE 92 | # instance_eval d 93 | #rescue SyntaxError 94 | # puts "This error may be avoidable by returning the failing duck type as the error message." 95 | # raise 96 | #end 97 | 98 | decoy 99 | end 100 | 101 | end # class MethodProbe 102 | 103 | end 104 | 105 | 106 | class ::Method 107 | 108 | # Outputs migration information. 109 | def migration 110 | parameters = []; argc = self.arity 111 | if argc > 0 112 | argc.times { parameters << Quarry::Probe.new } 113 | Probe.duckcall { self.call(*parameters) } 114 | elsif argc < 0 115 | raise "(NYI) method takes unlimited arguments" 116 | end 117 | return parameters 118 | end 119 | private :migration 120 | 121 | # Outputs signiture information. 122 | def signature(detail=nil) 123 | ds = [] 124 | case detail 125 | when :complete, :all, :full 126 | ds = migration 127 | when :class, :with_class 128 | migration.each { |dh| ds << dh.ducks } 129 | when :pp, :pretty, :prettyprint, :pretty_print 130 | migration.each do |dh| 131 | responders = [] 132 | dh.ducks.each do |responder, argss| 133 | argss.each { |args| responders << "#{responder}( #{args.join(',')} )" } 134 | end 135 | ds << responders 136 | end 137 | else 138 | migration.each { |dh| ds << dh.ducks.keys } 139 | end 140 | return ds 141 | end 142 | 143 | end 144 | 145 | 146 | 147 | 148 | 149 | =begin test 150 | 151 | require 'test/unit' 152 | 153 | # " I am a Duck Hunter ! " 154 | 155 | class TC_MethodProbe < Test::Unit::TestCase 156 | 157 | # fixture 158 | def amethod(x) 159 | x + 1 160 | end 161 | 162 | def test_signiture_default 163 | assert_nothing_raised { 164 | method(:amethod).signature 165 | } 166 | end 167 | 168 | def test_signiture_with_class 169 | assert_nothing_raised { 170 | method(:amethod).signature(:class) 171 | } 172 | end 173 | 174 | def test_signiture_pp 175 | assert_nothing_raised { 176 | method(:amethod).signature(:pp) 177 | } 178 | end 179 | 180 | def test_signiture_all 181 | assert_nothing_raised { 182 | method(:amethod).signature(:complete) 183 | } 184 | end 185 | 186 | end 187 | 188 | =end 189 | 190 | # Copyright (c) 2004,2008 Thomas Sawyer 191 | 192 | -------------------------------------------------------------------------------- /lib/qed/reporter/tapy.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | module Reporter #:nodoc: 3 | 4 | require 'qed/reporter/abstract' 5 | 6 | # TAP-Y Reporter 7 | # 8 | # NOTE: I suppose techincally that each TAP-Y test should be an assertion, 9 | # but that's a whole other ball of wax, and would require AE to remember 10 | # every assertion made. It also would have no means of providing an upfront 11 | # count. 12 | # 13 | class TapY < Abstract 14 | 15 | # 16 | def before_session(session) 17 | @start_time = Time.now 18 | 19 | data = { 20 | 'type' => 'suite', 21 | 'start' => Time.now.strftime('%Y-%m-%d %H:%M:%S'), 22 | 'count' => session.total_step_count, 23 | 'rev' => 2 24 | } 25 | io.puts data.to_yaml 26 | end 27 | 28 | # TODO: Handle cases by file or by headers? 29 | def demo(demo) 30 | data = { 31 | 'type' => 'case', 32 | 'subtype' => 'demo', 33 | 'label' => localize_file(demo.file), 34 | 'level' => 0 35 | } 36 | io.puts data.to_yaml 37 | end 38 | 39 | # 40 | # @todo How to get the line number so we can do proper snippets? 41 | def pass(step) 42 | super(step) 43 | 44 | source_line = lines = step.text.split("\n") 45 | 46 | #snip, l = [], step.line 47 | #lines.map do |line| 48 | # snip << { (l += 1) => line } 49 | #end 50 | 51 | #if step.header? 52 | # data = { 53 | # 'type' => 'note', 54 | # 'description' => step.text, #.strip, 55 | # } 56 | 57 | data = { 58 | 'type' => 'test', 59 | 'subtype' => 'step', 60 | 'status' => 'pass', 61 | 'label' => step.text.strip, 62 | 'file' => localize_file(step.file), 63 | 'line' => step.lineno, 64 | 'time' => time_since_start 65 | } 66 | 67 | #'returned' => nil, 68 | #'expected' => nil, 69 | 70 | if step.example? 71 | if step.code? 72 | data.merge!( 73 | 'source' => step.example_lines.first.last.strip, 74 | 'snippet' => step.example_lines.map{ |n, l| {n => l.rstrip} } 75 | ) 76 | else 77 | data.merge!( 78 | 'source' => step.example_lines.first.last.strip, 79 | 'snippet' => step.example_lines.map{ |n, l| {n => l.rstrip} } 80 | ) 81 | end 82 | else 83 | #data.merge!( 84 | # 'source' => step.explain_lines.first.first, 85 | # 'snippet' => step.sample_text 86 | #) 87 | end 88 | 89 | io.puts data.to_yaml 90 | end 91 | 92 | # 93 | def fail(step, assertion) 94 | super(step, assertion) 95 | 96 | backtrace = sane_backtrace(assertion) 97 | 98 | file, line = file_line(backtrace) 99 | file = localize_file(file) 100 | 101 | snippet = structured_code_snippet(assertion, bredth=3) 102 | source = snippet.map{ |h| h.values.first }[snippet.size / 2].strip 103 | 104 | data = { 105 | 'type' => 'test', 106 | 'subtype' => 'step', 107 | 'status' => 'fail', 108 | 'label' => step.explain.strip, 109 | 'file' => localize_file(step.file), 110 | 'line' => step.explain_lineno, 111 | #'returned' => nil, 112 | #'expected' => nil, 113 | 'time' => time_since_start, 114 | 'exception' => { 115 | 'message' => assertion.message, #unansi 116 | 'class' => assertion.class.name, 117 | 'file' => file, 118 | 'line' => line, 119 | 'source' => source, 120 | 'snippet' => snippet, 121 | 'backtrace' => backtrace 122 | } 123 | } 124 | 125 | io.puts data.to_yaml 126 | end 127 | 128 | # 129 | def error(step, exception) 130 | super(step, exception) 131 | 132 | backtrace = sane_backtrace(exception) 133 | 134 | file, line = file_line(backtrace) 135 | file = localize_file(file) 136 | 137 | snippet = structured_code_snippet(exception, bredth=3) 138 | source = snippet.map{ |h| h.values.first }[snippet.size / 2].strip 139 | 140 | data = { 141 | 'type' => 'test', 142 | 'subtype' => 'step', 143 | 'status' => 'error', 144 | 'label' => step.explain.strip, 145 | 'file' => localize_file(step.file), 146 | 'line' => step.explain_lineno, 147 | #'returned' => nil, 148 | #'expected' => nil, 149 | 'time' => time_since_start, 150 | 'exception' => { 151 | 'message' => assertion.message, #unansi 152 | 'class' => assertion.class.name, 153 | 'file' => file, 154 | 'line' => line, 155 | 'source' => source, 156 | 'snippet' => snippet, 157 | 'backtrace' => backtrace 158 | } 159 | } 160 | 161 | io.puts data.to_yaml 162 | end 163 | 164 | # 165 | def after_session(session) 166 | pass_size = steps.size - (fails.size + errors.size + omits.size) 167 | 168 | data = { 169 | 'type' => 'final', 170 | 'time' => time_since_start, 171 | 'counts' => { 172 | 'total' => steps.size, 173 | 'pass' => pass_size, 174 | 'fail' => fails.size, 175 | 'error' => errors.size, 176 | 'omit' => omits.size, 177 | 'todo' => 0 178 | } 179 | } 180 | 181 | io.puts data.to_yaml 182 | end 183 | 184 | private 185 | 186 | # 187 | def time_since_start 188 | Time.now - @start_time 189 | end 190 | 191 | end 192 | 193 | end#module Reporter 194 | end#module QED 195 | -------------------------------------------------------------------------------- /lib/qed/scope.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | #require 'ae' 4 | 5 | # Scope is the context in which QED documents are run. 6 | # 7 | class Scope < Module 8 | 9 | # Location of `qed/scope.rb`. 10 | DIRECTORY = File.dirname(__FILE__) 11 | 12 | # Setup new Scope instance. 13 | def initialize(demo, options={}) 14 | super() 15 | 16 | @_applique = demo.applique_prime 17 | 18 | @_file = demo.file 19 | @_root = options[:root] || $ROOT # FIXME 20 | 21 | @_features = [] 22 | 23 | include *demo.applique 24 | 25 | # TODO: custom extends? 26 | 27 | __create_clean_binding_method__ 28 | end 29 | 30 | # This turns out to be the key to proper scoping. 31 | def __create_clean_binding_method__ 32 | module_eval %{ 33 | def __binding__ 34 | @__binding__ ||= binding 35 | end 36 | } 37 | end 38 | 39 | # 40 | def include(*modules) 41 | super(*modules) 42 | extend self # overcome dynamic inclusion problem 43 | end 44 | 45 | # Expanded dirname of +file+. 46 | def demo_directory 47 | @_demo_directory ||= File.expand_path(File.dirname(@_file)) 48 | end 49 | 50 | # Evaluate code in the context of the scope's special binding. 51 | # The return value of the evaluation is stored in `@_`. 52 | # 53 | def evaluate(code, file=nil, line=nil) 54 | if file 55 | @_ = eval(code, __binding__, file.to_s, line.to_i) 56 | else 57 | @_ = eval(code, __binding__) 58 | end 59 | end 60 | 61 | # TODO: Alternative to Plugin gem? If not improve and make standard requirement. 62 | 63 | # Utilize is like #require, but will evaluate the script in the context 64 | # of the current scope. 65 | # 66 | def utilize(file) 67 | file = Dir[DIRECTORY + "/helpers/#{file}"].first 68 | if !file 69 | require 'plugin' 70 | file = Plugin.find("#{file}{,.rb}", :directory=>nil) 71 | end 72 | if file && !@_features.include?(file) 73 | code = File.read(file) 74 | evaluate(code, nil, file) 75 | else 76 | raise LoadError, "no such file -- #{file}" 77 | end 78 | end 79 | 80 | # Define "when" advice. 81 | def When(*patterns, &procedure) 82 | #patterns = patterns.map{ |pat| pat == :text ? :desc : pat } 83 | @_applique.When(*patterns, &procedure) 84 | end 85 | 86 | # Define "before" advice. Default type is :each, which 87 | # evaluates just before example code is run. 88 | def Before(type=:each, &procedure) 89 | type = :step if type == :each 90 | type = :demo if type == :all 91 | @_applique.Before(type, &procedure) 92 | end 93 | 94 | # Define "after" advice. Default type is :each, which 95 | # evaluates just after example code is run. 96 | def After(type=:each, &procedure) 97 | type = :step if type == :each 98 | type = :demo if type == :all 99 | @_applique.After(type, &procedure) 100 | end 101 | 102 | # Directory of current document. 103 | def __DIR__(file=nil) 104 | if file 105 | Dir.glob(File.join(File.dirname(@_file), file)).first || file 106 | else 107 | File.dirname(@_file) 108 | end 109 | end 110 | 111 | # TODO: Should Table and Data be extensions that can be optionally loaded? 112 | 113 | # TODO: Cache Table and Data for speed ? 114 | 115 | # Use sample table to run steps. The table file is located relative to 116 | # the demo, failing that it will be looked for relative to the working 117 | # directory. 118 | # 119 | def Table(file=nil, options={}) #:yield: 120 | if file 121 | file = Dir.glob(File.join(File.dirname(@_file), file)).first || file 122 | else 123 | file = @_last_table 124 | end 125 | @_last_table = file 126 | 127 | file_handle = File.new(file) 128 | 129 | if options[:stream] 130 | if block_given? 131 | YAML.load_documents(file_handle) do |data| 132 | yield data 133 | end 134 | else 135 | YAML.load_stream(file_handle) 136 | end 137 | else 138 | if block_given? 139 | tbl = YAML.load(file_handle) 140 | tbl.each do |data| 141 | yield(*data) 142 | end 143 | else 144 | YAML.load(file_handle) 145 | end 146 | end 147 | end 148 | 149 | # Read a static data file and yield contents to block if given. 150 | # 151 | # This method no longer automatically uses YAML.load. 152 | def Data(file) #:yield: 153 | file = Dir.glob(File.join(File.dirname(@_file), file)).first || file 154 | #case File.extname(file) 155 | #when '.yml', '.yaml' 156 | # data = YAML.load(File.new(file)) 157 | #else 158 | data = File.read(file) 159 | #end 160 | if block_given? 161 | yield(data) 162 | else 163 | data 164 | end 165 | end 166 | 167 | # Helper method to clear temporary work directory. 168 | # 169 | def clear_working_directory! 170 | dir = @_root 171 | dir = File.expand_path(dir) 172 | 173 | if dir == '/' or dir == File.expand_path('~') 174 | abort "DANGER! Trying to use home or root as a temporary directory!" 175 | end 176 | 177 | entries = Dir.glob(File.join(dir, '**/*')) 178 | 179 | dirs, files = entries.partition{ |f| File.directory?(f) } 180 | 181 | files.each { |file| FileUtils.rm(file) } 182 | dirs.each { |dir| FileUtils.rmdir(dir) } 183 | end 184 | 185 | # TODO: Project's root directory. 186 | #def rootdir 187 | # @_root 188 | #end 189 | 190 | # Redirect constant missing to toplevel (i.e. Object). This is 191 | # to allow the evaluation scope to emulate the toplevel. 192 | def const_missing(const) 193 | Object.const_get(const) 194 | end 195 | 196 | end#class Scope 197 | 198 | end#module QED 199 | -------------------------------------------------------------------------------- /work/Notes.rdoc: -------------------------------------------------------------------------------- 1 | = Diary 2 | 3 | == 2010-06-06 / HTML Won't Do 4 | 5 | There have proven to be significant issues associated with translating markup to HTML and having QED evalute the HTML, rather than plain text. To begin with different markup engines translate special characeters in differnt ways. RDoc for instance will translate astrisks around a word into 'b' tags, whereas Textile translates them to 'em' tags. Still other characters are translated into HTML escape codes, eg. \&#8216;. There are a variety of these and each markup language will translate them according to it's own rules (or not at all). This means pattern matches in advice have to take these translations into account. Instead of matching on an asterisk we might need to match on ++. A minor naggle perhaps, but it means being exceptionally aware of all translaterions that occur, and moreover, that applique may not be portable across markup languages. 6 | 7 | There are other discrepencies as well such as the translation of code blocks to \
 vs. \
\ tags, but all this aside, while perhaps annoying, it can be managed. A normalization procedure can help mitage the issues. However, the ultimate problem is your classic YAGNI, the fact that little, if anything, is gained by using HTML as the defacto document format. And this is simply because everyone will write there documents in a text-based markup, none of which fully support the HTML that QED could utilize. For instance RDoc and Markdown do not have a notation for tables (although extended Markdown might). Textile does not have a clean notation for \
 blocks --you have to use \
! And ASCIIDoc, while a very capable documentation system has a verbose syntax that is not particullary suited to reading in plain text. Perhaps if all the available markdown languages were equally as capable, it would be worth the additional effort. But lacking this I beleive more advantage can ultimately be gained by allowing QED to have it's own markup syntax one suited best to it's purpose.
  8 | 
  9 | 
 10 | == 2010-02-03 / Some Determinations
 11 | 
 12 | In the end I have decided on loading helpers on the fly via
 13 | special AHREF links. We still need a plan for loading global
 14 | helpers though.
 15 | 
 16 | Also, per last entry, Before and After stays b/c When doesn't
 17 | quite cover the usecase. However, I definately encourge the use
 18 | of helper methods in place of Before and After clauses.
 19 | 
 20 | 
 21 | == 2010-01-28 / Before and After
 22 | 
 23 | Wouldn't it better to allow helpers to define methods, which
 24 | can be called in the steps for setting things up, rather than
 25 | using "magic" Before and After clauses which give no indication
 26 | that something has happened?
 27 | 
 28 |   = Examples
 29 | 
 30 |   == Example #1
 31 | 
 32 |   For example #1 we will use example1[helper/example1] support file.
 33 |   First we need to prepare the example. It is a complex process, so we
 34 |   have made a special method for it. See the helper[helper/example1]
 35 |   file for details.
 36 | 
 37 |     ex = prepare_example1
 38 | 
 39 |   Now we can show that the example is hip.
 40 | 
 41 |     ex.assert.hip == true
 42 | 
 43 | So we could get rid of Before(:step) and After(:step) definitions this
 44 | way, and it's not such a big loss, b/c we still have #When, and 
 45 | After(:Step) is of very rare utility which can be worked around.
 46 | 
 47 | 
 48 | == 2010-01-30 / The Best Way to Handle Helpers
 49 | 
 50 | 1) Should helpers be stored in a special location relative
 51 | to the demo file, from which all helpers in that location
 52 | are loaded automatically. This simplifies loading, but it
 53 | makes support for per-demo helpers more awkward.
 54 | This option also means taking special consideration for
 55 | helpers when documenting --any reference to them will have
 56 | to be tacked-on rather then be an integral part of the document
 57 | itself.
 58 | 
 59 | We might do something like this:
 60 | 
 61 |   demos/
 62 |     demo1.rdoc
 63 |     demo1/
 64 |       helper.rb
 65 |     demo2.rdoc
 66 |     demo2/
 67 |       helper.rb
 68 |     share/
 69 |       helper.rb
 70 | 
 71 | Such that demo1/helper.rb only applies to demo1.rdoc, and 
 72 | likewise for demo2, but both use share/helper.rb.
 73 | 
 74 | 2) Or, should the demo be able to specify which helpers to
 75 | load via href links. This allows full flexability it selecting
 76 | behavior for the demo. It also means the documentation will
 77 | have references to helpers built-in (although they will have
 78 | to be augmented when documenting to ensure the hrefs link
 79 | correctly). But session-oriented advice doesn't make much
 80 | sense in a per-demo context. We could ignore that, or place
 81 | session advice in a separate location. While this option
 82 | is more flexible, it also means demos may be more difficult
 83 | to follow, because one must scan the links in the document
 84 | to determine which helpers are being used.
 85 | 
 86 | An example demo might look like:
 87 | 
 88 |   == Examples
 89 | 
 90 |   == Example 1
 91 | 
 92 |   For example #1 we will use example1[helper/example1] support file.
 93 | 
 94 |   ... and so on ...
 95 | 
 96 |   == Example 2
 97 |   
 98 |   For example #2 we will use example2[helper/example2] support file.
 99 |   
100 |   ... and so on ...
101 | 
102 | In which case it seems prudent to load these helpers as they are 
103 | evaluated, rather than all at once at the start. However this insinuates
104 | that there is only one before and after advice at a time, and when
105 | would before advice for the entire demo run, upon loading the helper?
106 | 
107 | I think it would be better to use When clauses. Then the helpers could
108 | be loaded all at once at the start and it would not matter. And instead of
109 | Before do use When /*/ do for general before
110 | clauses.
111 | 
112 | However, if referenced helpers are to be loaded all at once at the start,
113 | it seems almost silly to even allow helpers to be referenced in the
114 | document. With the exception of using them as footnotes, it conveys too
115 | much positional indication. So either load them when they are encountered,
116 | or use option #1.
117 | 
118 | 


--------------------------------------------------------------------------------
/lib/qed/step.rb:
--------------------------------------------------------------------------------
  1 | module QED
  2 | 
  3 |   # A Step consists of a flush region of text and the indented 
  4 |   # text the follows it. QED breaks all demos down into step
  5 |   # for evaluation.
  6 |   #
  7 |   # Steps form a doubly linkes list, each having access to the step
  8 |   # before and the step after them. Potentially this could be used
  9 |   # by very advnaced matchers, to vary executation by earlier or later
 10 |   # content of a demo.
 11 |   #
 12 |   class Step
 13 | 
 14 |     # Ths demo to which the step belongs.
 15 |     # @return [Demo] demo
 16 |     attr :demo
 17 | 
 18 |     # Block lines code/text.
 19 |     #attr :lines
 20 | 
 21 |     # Previous step.
 22 |     # @return [Step] previous step
 23 |     attr :back_step
 24 | 
 25 |     # Next step.
 26 |     # @return [Step] next step
 27 |     attr :next_step
 28 | 
 29 |     # Step up a new step.
 30 |     #
 31 |     # @param [Demo] demo
 32 |     #   The demo to which the step belongs.
 33 |     #
 34 |     # @param [Array]] explain_lines
 35 |     #   The step's explaination text, broken down into an array
 36 |     #   of `[line number, line text]` entries.
 37 |     #
 38 |     # @param [Array]] example_lines
 39 |     #   The steps example text, broken down into an array
 40 |     #   of `[line number, line text]` entries.
 41 |     #
 42 |     # @param [Step] last
 43 |     #   The previous step in the demo.
 44 |     #
 45 |     def initialize(demo, explain_lines, example_lines, last)
 46 |       #QED.all_steps << self
 47 | 
 48 |       @demo = demo
 49 |       @file = demo.file
 50 | 
 51 |       #@lines = []
 52 | 
 53 |       @explain_lines = explain_lines
 54 |       @example_lines = example_lines
 55 | 
 56 |       @back_step = last
 57 |       @back_step.next_step = self if @back_step
 58 |     end
 59 | 
 60 |     # The step's explaination text, broken down into an array
 61 |     # of `[line number, line text]` entries.
 62 |     #
 63 |     # @return [Array]] explain_lines
 64 |     attr :explain_lines
 65 | 
 66 |     # The steps example text, broken down into an array
 67 |     # of `[line number, line text]` entries.
 68 |     #
 69 |     # @return [Array]] example_lines
 70 |     attr :example_lines
 71 | 
 72 |     # Ths file to which the step belongs.
 73 |     #
 74 |     # @return [String] file path
 75 |     def file
 76 |       demo.file
 77 |     end
 78 | 
 79 |     # Full text of block including both explination and example text.
 80 |     def to_s
 81 |       (@explain_lines + @example_lines).map{ |lineno, line| line }.join("")
 82 |     end
 83 | 
 84 |     # Description text.
 85 |     def explain
 86 |       @explain ||= @explain_lines.map{ |lineno, line| line }.join("")
 87 |     end
 88 | 
 89 |     # Alternate term for #explain.
 90 |     alias_method :description, :explain
 91 | 
 92 |     # @deprecated
 93 |     alias_method :text, :explain
 94 | 
 95 |     # TODO: Support embedded rule steps ?
 96 | 
 97 |     #
 98 |     #def rule?
 99 |     #  @is_rule ||= (/\A(given|when|rule|before|after)[:.]/i.match(text))
100 |     #end
101 | 
102 |     #
103 |     #def rule_text
104 |     #  rule?.post_match.strip
105 |     #end
106 | 
107 |     # TODO: better name than :proc ?
108 | 
109 |     # 
110 |     def type
111 |       assertive? ? :test : :proc
112 |     end
113 | 
114 |     # A step is a heading if it's description starts with a '=' or '#'.
115 |     def heading?
116 |       @is_heading ||= (/\A[=#]/ =~ explain)
117 |     end
118 | 
119 |     # Any commentary ending in `:` will mark the example
120 |     # text as a plain text *sample* and not code to be evaluated.
121 |     def data?
122 |       @is_data ||= explain.strip.end_with?(':')
123 |     end
124 | 
125 |     # Is the example text code to be evaluated?
126 |     def code?
127 |       !data? && example?
128 |     end
129 | 
130 |     # First line of example text.
131 |     def lineno
132 |       @lineno ||= (
133 |         if @example_lines.first
134 |           @example_lines.first.first
135 |         elsif @explain_lines.first
136 |           @explain_lines.first.first
137 |          else
138 |           1
139 |         end
140 |       )
141 |     end
142 | 
143 |     def explain_lineno
144 |       @explain_lines.first ? @explain_lines.first.first : 1
145 |     end
146 | 
147 |     def example_lineno
148 |       @example_lines.first ? @example_lines.first.first : 1
149 |     end
150 | 
151 |     # Does the block have an example?
152 |     def example?
153 |       ! example.strip.empty?
154 |     end
155 |     alias has_example? example?
156 | 
157 |     #
158 |     def example
159 |       @example ||= (
160 |         if data?
161 |           @example_lines.map{ |lineno, line| line }.join("")
162 |         else
163 |           tweak_code
164 |         end
165 |       )
166 |     end
167 |     alias_method :code, :example
168 |     alias_method :data, :example
169 | 
170 |     # Returns any extra arguments the step passes along to rules.
171 |     def arguments
172 |       []
173 |     end
174 | 
175 |     # Clean up the example text, removing unccesseary white lines
176 |     # and triple quote brackets, but keep indention intact.
177 |     #
178 |     def clean_example
179 |       str = example.chomp.sub(/\A\n/,'')
180 |       if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str)
181 |         str = md[1]
182 |       end
183 |       str.rstrip
184 |     end
185 | 
186 |     # TODO: We need to preserve the indentation for the verbatim reporter.
187 |     #def clean_quote(text)
188 |     #  text = text.tabto(0).chomp.sub(/\A\n/,'')
189 |     #  if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
190 |     #    text = md[1]
191 |     #  end
192 |     #  text.rstrip
193 |     #end
194 | 
195 |     # When the text is sample text and passed to an adivce block, this
196 |     # provides the prepared form of the example text, removing white lines,
197 |     # triple quote brackets and indention.
198 |     #
199 |     def sample_text
200 |       str = example.tabto(0).chomp.sub(/\A\n/,'')
201 |       if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str)
202 |         str = md[1]
203 |       end
204 |       str.rstrip
205 |     end
206 | 
207 |     # TODO: object_hexid
208 |     def inspect
209 |       str = text[0,24].gsub("\n"," ")
210 |       %[\#]
211 |     end
212 | 
213 |     #
214 |     def assertive?
215 |       @assertive ||= !text.strip.end_with?('^')
216 |     end
217 | 
218 |   protected
219 | 
220 |     #
221 |     def next_step=(n)
222 |       @next_step = n
223 |     end
224 | 
225 |   private
226 | 
227 |     #
228 |     def tweak_code
229 |       code = @example_lines.map{ |lineno, line| line }.join("")
230 | 
231 |       #code.gsub!(/\n\s*\#\ ?\=\>(.*?)$/, ' == \1 ? assert(true) : assert(false, %{not returned -- \1})')   # TODO: what kind of error ?
232 |       #code.gsub!(/\s*\#\ ?\=\>(.*?)$/,   ' == \1 ? assert(true) : assert(false, %{not returned -- \1})')
233 | 
234 |       code.gsub!(/\n\s*\#\ ?\=\>\s*(.*?)$/, '.must_return(\1)')
235 |       code.gsub!(/\s*\#\ ?\=\>\s*(.*?)$/, '.must_return(\1)')
236 | 
237 |       code
238 |     end
239 | 
240 |   end
241 | 
242 | end
243 | 


--------------------------------------------------------------------------------
/lib/qed/session.rb:
--------------------------------------------------------------------------------
  1 | module QED
  2 | 
  3 |   require 'qed/settings'
  4 |   require 'qed/demo'
  5 | 
  6 |   def self.run!(name=nil, &block)
  7 |     configure(name, &block) if block
  8 |     session  = Session.new(:profile=>name)
  9 |     success  = session.run
 10 |     exit -1 unless success
 11 |   end
 12 | 
 13 |   # The Session class encapsulates a set of demonstrations 
 14 |   # and the procedure for looping through them and running
 15 |   # each in turn.
 16 |   #
 17 |   class Session
 18 | 
 19 |     # Default recognized demos file types.
 20 |     DEMO_TYPES = %w{qed rdoc md markdown}
 21 | 
 22 |     #
 23 |     CODE_TYPES = %w{rb}
 24 | 
 25 |     # Returns instance of Settings class.
 26 |     attr :settings
 27 | 
 28 |     # New Session
 29 |     def initialize(settings={})
 30 |       require_reporters
 31 | 
 32 |       case settings
 33 |       when Settings
 34 |         @settings = settings
 35 |       else
 36 |         @settings = Settings.new(settings)
 37 |       end
 38 |     end
 39 | 
 40 |     # Demonstration files (or globs).
 41 |     def files
 42 |       settings.files
 43 |     end
 44 | 
 45 |     # File patterns to omit.
 46 |     def omit
 47 |       settings.omit
 48 |     end
 49 | 
 50 |     # Output format.
 51 |     def format
 52 |       settings.format
 53 |     end
 54 | 
 55 |     # Trace execution?
 56 |     def trace?
 57 |       settings.trace
 58 |     end
 59 | 
 60 |     # Parse mode.
 61 |     def mode
 62 |       settings.mode
 63 |     end
 64 | 
 65 |     # Paths to be added to $LOAD_PATH.
 66 |     def loadpath
 67 |       settings.loadpath
 68 |     end
 69 | 
 70 |     # Libraries to be required.
 71 |     def requires
 72 |       settings.requires
 73 |     end
 74 | 
 75 |     # Operate from project root?
 76 |     def rooted
 77 |       settings.rooted
 78 |     end
 79 | 
 80 |     # Selected profile.
 81 |     def profile
 82 |       settings.profile
 83 |     end
 84 | 
 85 |     #
 86 |     def directory
 87 |       settings.tmpdir
 88 |     end
 89 | 
 90 |     # Top-level configuration.
 91 |     #def config
 92 |     #  QED.config
 93 |     #end
 94 | 
 95 |     # TODO: Ultimately use a plugin library to support custom reporters?
 96 | 
 97 |     # Require all reporters.
 98 |     def require_reporters
 99 |       Dir[File.dirname(__FILE__) + '/reporter/*'].each do |file|
100 |         require file
101 |       end
102 |     end
103 | 
104 |     # Instance of selected Reporter subclass.
105 |     def reporter
106 |       @reporter ||= (
107 |         name = Reporter.constants.find{ |c| /#{format}/ =~ c.to_s.downcase }
108 |         Reporter.const_get(name).new(:trace => trace?)
109 |       )
110 |     end
111 | 
112 |     # TODO: Pass settings to demo, so we can get temporary_directory.
113 | 
114 |     # Returns an Array of Demo instances.
115 |     def demos
116 |       @demos ||= demo_files.map{ |file| Demo.new(file, :mode=>mode, :at=>directory) }
117 |     end
118 | 
119 |     # List of observers to pass to the evaluator. Only includes the reporter
120 |     # instance, by default.
121 |     #
122 |     def observers
123 |       [reporter]
124 |     end
125 | 
126 |     # TODO: remove loadpath additions when done
127 | 
128 |     # Run session.
129 |     def run
130 |       abort "No documents." if demo_files.empty?
131 | 
132 |       clear_directory
133 | 
134 |       reset_assertion_counts
135 | 
136 |       #require_profile  # <-- finally runs the profile
137 | 
138 |       prepare_loadpath
139 |       require_libraries
140 | 
141 |       Dir.chdir(directory) do
142 |         # pre-parse demos
143 |         demos.each{ |demo| demo.steps }
144 | 
145 |         # Let's do it.
146 |         observers.each{ |o| o.before_session(self) }
147 |         begin
148 |           demos.each do |demo|
149 |             Evaluator.run(demo, :observers=>observers, :settings=>settings) #demo.run(*observers)
150 |             #pid = fork { demo.run(*observers) }
151 |             #Process.detach(pid)
152 |           end
153 |         ensure
154 |           observers.each{ |o| o.after_session(self) }
155 |         end
156 |       end
157 | 
158 |       reporter.success?
159 |     end
160 | 
161 |     # Clear temporary testing directory.
162 |     def clear_directory
163 |       settings.clear_directory
164 |     end
165 | 
166 |     # Set $ASSERTION_COUNTS to zero point.
167 |     def reset_assertion_counts
168 |       $ASSERTION_COUNTS = Hash.new{ |h,k| h[k] = 0 }
169 |     end
170 | 
171 |     # Add to load path (from -I option).
172 |     def prepare_loadpath
173 |       loadpath.each{ |dir| $LOAD_PATH.unshift(File.expand_path(dir)) }
174 |     end
175 | 
176 |     # Require libraries (from -r option).
177 |     def require_libraries
178 |       requires.each{ |file| require(file) }
179 |     end
180 | 
181 |     #
182 |     #def require_profile
183 |     #  settings.load_profile(profile)
184 |     #end
185 | 
186 |     # Returns a list of demo files. The files returned depends on the
187 |     # +files+ attribute and if none given, then the current run mode.
188 |     def demo_files
189 |       @demo_files ||= (
190 |         if mode == :comment
191 |           demo_files_in_comment_mode
192 |         else
193 |           demo_files_in_normal_mode
194 |         end
195 |       )
196 |     end
197 | 
198 |     # Collect default files to process in normal demo mode.
199 |     def demo_files_in_normal_mode
200 |       demos_gather #(DEMO_TYPES)
201 |     end
202 | 
203 |     # Collect default files to process in code comment mode.
204 |     #
205 |     # TODO: Sure removing applique files is the best approach here?
206 |     def demo_files_in_comment_mode
207 |       files = demos_gather(CODE_TYPES)
208 |       files = files.reject{ |f| f.index('applique/') }  # don't include applique files ???
209 |       files
210 |     end
211 | 
212 |     # Gather list of demo files. Uses +omit+ to remove certain files
213 |     # based on the name of their parent directory.
214 |     def demos_gather(extensions=DEMO_TYPES)
215 |       files = self.files
216 |       #files << default_location if files.empty?
217 |       files = files.map{|pattern| Dir[pattern]}.flatten.uniq
218 |       files = files.map do |file|
219 |         if File.directory?(file)
220 |           Dir[File.join(file,'**','*.{' + extensions.join(',') + '}')]
221 |         else
222 |           file
223 |         end
224 |       end
225 |       files = files.flatten.uniq
226 |       #files = files.reject{ |f| f =~ Regexp.new("/\/(#{omit.join('|')})\//") }
227 |       files = files.reject{ |f| omit.any?{ |o| f.index("/#{o}/") } }
228 |       files.map{|f| File.expand_path(f) }.uniq.sort
229 |     end
230 | 
231 |     # Globally applicable advice.
232 |     #def environment
233 |     #  scripts.each do |script|
234 |     #    script.require_environment
235 |     #  end
236 |     #end
237 | 
238 |     # Get the total test count. This method tallies up the number of
239 |     # _assertive_ steps.
240 |     #
241 |     def total_step_count
242 |       count = 0
243 |       demos.each do |demo|
244 |         demo.steps.each do |step|
245 |           count += 1 if step.assertive?
246 |         end
247 |       end
248 |       count
249 |     end
250 | 
251 |   end#class Session
252 | 
253 | end#module QED
254 | 


--------------------------------------------------------------------------------
/lib/qed/document.rb:
--------------------------------------------------------------------------------
  1 | require 'erb'
  2 | require 'fileutils'
  3 | #require 'nokogiri'
  4 | 
  5 | module QED
  6 | 
  7 |   # = Document
  8 |   #
  9 |   # TODO: css and javascripts have fixed location need to make more flexible.
 10 |   # TODO: Have option to run documents through the runner and color code output; need htmlreporter.
 11 |   #
 12 |   class Document
 13 | 
 14 |     DEFAULT_TITLE  = "Demonstration"
 15 |     DEFAULT_CSS    = nil #"../assets/styles/spec.css"
 16 |     DEFAULT_OUTPUT = "qed.html"
 17 |     DEFAULT_PATH   = "qed"
 18 | 
 19 |     attr_accessor :title
 20 | 
 21 |     attr_accessor :css
 22 | 
 23 |     attr_accessor :dryrun
 24 | 
 25 |     attr_accessor :quiet
 26 | 
 27 |     # Ouput file.
 28 |     attr_accessor :output
 29 | 
 30 |     # Format of output file, either 'html' or 'plain'.
 31 |     # Defaults to extension of output file.
 32 |     attr_accessor :format
 33 | 
 34 |     # Files to document.
 35 |     attr_reader :paths
 36 | 
 37 |     #
 38 |     def paths=(paths)
 39 |       @paths = [paths].flatten
 40 |     end
 41 | 
 42 |     # New Spec Document object.
 43 |     def initialize(options={})
 44 |       options.each do |k,v|
 45 |         __send__("#{k}=", v)
 46 |       end
 47 | 
 48 |       @paths  ||= []
 49 | 
 50 |       @output ||= DEFAULT_OUTPUT
 51 |       @title  ||= DEFAULT_TITLE
 52 |       @css    ||= DEFAULT_CSS
 53 | 
 54 |       if File.directory?(@output)
 55 |         @output = File.join(@output, 'qed.html')
 56 |       end
 57 | 
 58 |       @format ||= File.extname(@output).sub('.','')
 59 | 
 60 |       if @paths.empty?
 61 |         #dir = Dir['{test/demos,demos,demo}'].first || DEFAULT_PATH
 62 |         #@paths  = File.join(dir, '**', '*')
 63 |         abort "No files to document."
 64 |       end
 65 |     end
 66 | 
 67 |     # Demo files.
 68 |     def demo_files
 69 |       @demo_files ||= (
 70 |         files = []
 71 |         paths.each do |f|
 72 |           if File.directory?(f)
 73 |             files.concat Dir[File.join(f,'**','*')]
 74 |           else
 75 |             files.concat Dir[f]
 76 |           end
 77 |         end
 78 |         files = files.reject{ |f| File.directory?(f) }
 79 |         files = files.reject{ |f| File.extname(f) == '.rb' }
 80 |         files = files.reject{ |f| /(fixtures|helpers)\// =~ f }
 81 | 
 82 |         # doesn't include .rb applique but does markup applique
 83 |         applique, files = files.partition{ |f| /applique\// =~ f }
 84 | 
 85 |         applique.sort + files.sort
 86 |       )
 87 |     end
 88 | 
 89 |     # Supress output.
 90 |     def quiet?
 91 |       @quiet
 92 |     end
 93 | 
 94 |     # Generate specification document.
 95 |     #
 96 |     #--
 97 |     # TODO: Use Malt to support more formats in future.
 98 |     #++
 99 |     def generate
100 |       #copy_support_files
101 | 
102 |       out   = ''
103 |       files = []
104 | 
105 |       #paths.each do |path|
106 |       #  files.concat(Dir.glob(path).select{ |f| File.file?(f) })
107 |       #end
108 |       #files.sort!
109 | 
110 |       if dryrun or $DEBUG
111 |         puts demo_files.sort.join(" ")
112 |       end
113 | 
114 |       demo_files.each do |file|
115 |         #strio = StringIO.new('')
116 |         #reporter = Reporter::Html.new(strio)
117 |         #runner = Runner.new([file], reporter)
118 |         #runner.check
119 |         #iotext = strio.string
120 |         #strio.close
121 | 
122 |         ext = File.extname(file)
123 |         txt = File.read(file)
124 | 
125 |         if ext == '.qed'
126 |           ext = file_type(txt)
127 |         end
128 | 
129 |         #text = Tilt.new(file).render
130 |         #html = Nokogiri::HTML(text)
131 |         #body = html.css("body")
132 | 
133 |         text = ""
134 |         case ext
135 |         #when '.qed'
136 |         #  require_qedoc
137 |         #  markup = Markup.new(File.read(file))
138 |         #  text << markup.to_html
139 |         when '.rd', '.rdoc'
140 |           require_rdoc
141 |           require_qedoc
142 |           if html?
143 |             markup = Markup.new(txt)
144 |             text << markup.to_html
145 |             #text << markup.convert(iotext, formatter)
146 |           else
147 |             text << txt
148 |           end        
149 |         when '.md', '.markdown'
150 |           require_rdiscount
151 |           if html?
152 |             markdown = RDiscount.new(txt)
153 |             text << markdown.to_html
154 |           else
155 |             text << txt
156 |           end
157 |         end
158 | 
159 |         # TODO: Use Nokogiri to find all 
's with preceeding 

's that have text ending in `:`, and 160 | # add the class `no-highlight`. If no preceeding `:` add class ruby. 161 | 162 | out << "#{text}\n" 163 | end 164 | 165 | if html? 166 | temp = Template.new(template, out, title, css) 167 | html = temp.parse_template 168 | save(html) 169 | else 170 | save(out) 171 | end 172 | end 173 | 174 | # 175 | def html? 176 | format == 'html' 177 | end 178 | 179 | # 180 | #def copy_support_files 181 | # make_output_directory 182 | # %w{jquery.js}.each do |fname| 183 | # file = File.join(File.dirname(__FILE__), 'document', fname) 184 | # FileUtils.cp(file, output) 185 | # end 186 | #end 187 | 188 | # Load specification HTML template. 189 | def template 190 | @template ||= ( 191 | file = File.join(File.dirname(__FILE__), 'document', 'template.rhtml') 192 | File.read(file) 193 | ) 194 | end 195 | 196 | # Save specification document. 197 | def save(text) 198 | if dryrun 199 | puts "[dry-run] Write #{output}" unless quiet 200 | else 201 | make_output_directory 202 | File.open(output, 'wb') do |f| 203 | f << text 204 | end 205 | puts "Write #{output}" unless quiet 206 | end 207 | end 208 | 209 | def make_output_directory 210 | dir = File.dirname(output) 211 | FileUtils.mkdir_p(dir) unless File.directory?(dir) 212 | end 213 | 214 | private 215 | 216 | # 217 | def file_type(text) 218 | rdoc = text.index(/^\=/) 219 | markdown = text.index(/^\#/) 220 | if markdown && rdoc 221 | rdoc < markdown ? '.rdoc' : '.markdown' 222 | elsif rdoc 223 | '.rdoc' 224 | elsif markdown 225 | '.markdown' 226 | else # fallback to rdoc 227 | '.rdoc' 228 | end 229 | end 230 | 231 | # 232 | def require_qedoc 233 | @require_qedoc ||= ( 234 | require 'qed/document/markup' 235 | true 236 | ) 237 | end 238 | 239 | # 240 | def require_rdoc 241 | @require_rdoc ||= ( 242 | begin 243 | require 'rdoc/markup/to_html' 244 | rescue LoadError 245 | require 'rubygems' 246 | gem 'rdoc' 247 | retry 248 | end 249 | true 250 | ) 251 | end 252 | 253 | # 254 | def require_rdiscount 255 | @require_rdiscount ||= ( 256 | require 'rdiscount' 257 | true 258 | ) 259 | end 260 | 261 | end 262 | 263 | # = Document Template 264 | # 265 | class Template 266 | attr :spec 267 | attr :title 268 | attr :css 269 | 270 | # 271 | def initialize(template, spec, title, css) 272 | @template = template 273 | @spec = spec 274 | @title = title 275 | @css = css 276 | end 277 | 278 | def parse_template 279 | erb = ERB.new(@template) 280 | erb.result(binding) 281 | end 282 | end 283 | 284 | end 285 | 286 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby Q.E.D. 2 | 3 | [Homepage](http://rubyworks.github.com/qed) / 4 | [Documentation](http://rubydoc.info/gems/qed/frames) / 5 | [Report Issue](http://github.com/rubyworks/qed/issues) / 6 | [Development](http://github.com/rubyworks/qed) / 7 | [Mailing list](http://groups.google.com/group/rubyworks-mailinglist)     8 | [![Build Status](https://secure.travis-ci.org/rubyworks/qed.png)](http://travis-ci.org/rubyworks/qed) 9 | [![Gem Version](https://badge.fury.io/rb/qed.png)](http://badge.fury.io/rb/qed) 10 | 11 | 12 | ## Introduction 13 | 14 | Q.E.D. is an abbreviation for the well known Latin phrase "Quod Erat Demonstrandum", 15 | literally "which was to be demonstrated", which is oft written in its abbreviated 16 | form at the end of a mathematical proof or philosophical argument to signify 17 | a successful conclusion. And so it is too for Ruby Q.E.D., though it might as easily 18 | be taken to stand for "Quality Ensured Documentation". 19 | 20 | QED is in fact both a test framework and a documentation system for Ruby 21 | developers. QED sits somewhere between lower-level testing tools like Test::Unit 22 | and grandiose requirement specifications systems like Cucumber. In practice it 23 | works exceptionally well for API-Driven Design, which is especially 24 | useful when designing reusable libraries, but it can be used to test code at 25 | any level of abstraction, from unit test to systems tests. 26 | 27 | 28 | ## Features 29 | 30 | * Write tests and documentation in the same breath! 31 | * Demos can be RDoc, Markdown or any other conforming text format. 32 | * Can use any BRASS compliant assertion framework, such as the the excellent AE (Assertive Expressive) library. 33 | * Data and Table macros allows large sets of data to be tested by the same code. 34 | * Documentation tool provides nice output with jQuery-based TOC. 35 | 36 | 37 | ## Synopsis 38 | 39 | ### Assertion Syntax 40 | 41 | QED can use any BRASS compliant assertions framework. Simply require the library in 42 | ones applique (see below). Traditionally this has been the AE (Assertive Expressive) library, 43 | which provides an elegant means to make assertions. To give a quick overview, assertion 44 | can be written as: 45 | 46 | 4.assert == 5 47 | 48 | In this example, because 4 != 5, this expression will raise an Assertion 49 | exception. QED's Runner class is thus just a means of running and capturing 50 | code blocks containing such assertions. 51 | 52 | You can learn more about BRASS and AE at http://rubyworks.github.com/brass and 53 | http://rubyworks.github.com/ae, repectively. 54 | 55 | ### Document Structure 56 | 57 | QED documents are simply text files called *demonstrandum* (demos for short). 58 | Because they largely consist of free-form descriptive text, they are a practice 59 | pure Literate Programming. For example: 60 | 61 | = Example 62 | 63 | Shows that the number 5 does not equal 4. 64 | 65 | 5.assert! == 4 66 | 67 | But in fact equals 5. 68 | 69 | 5.assert == 5 70 | 71 | In this example RDoc was chosen for the document format. However, almost any 72 | text format can be used. The only necessary distinction is that description text 73 | align to the left margin and all code be indented, although QED does recognize 74 | RDoc and Markdown single-line style headers, so any format that supports 75 | those (which covers many markup formats in use today) will have mildly 76 | improved console output. In any case, the essential take away here is that 77 | QED *demonstrandum* are simply descriptive documents with interspersed 78 | blocks of example code. 79 | 80 | Give this design some thought. It should become clear that this approach is 81 | especially fruitful in that it allows *documentation* and *specification* 82 | to seamlessly merge into a unified *demonstration*. 83 | 84 | ### Running Demonstrations 85 | 86 | If we were to run the above document through QED in verbatim mode the output 87 | would be identical (assuming we did not make a typo and the assertions passed). 88 | If there were errors or failures, we would see information detailing each. 89 | 90 | To run a document through QED, simply use the +qed+ command. 91 | 92 | $ qed -v demo/01_example.rdoc 93 | 94 | The `-v` option specifies verbatim mode, which outputs the entire 95 | document. 96 | 97 | Notice we placed the QED document in a `demo/` directory. This is the 98 | canonical location, but there is no place that demonstrations have to go. They 99 | can be placed anywhere that is preferred. However, the `qed` command 100 | will look for `qed/`, `demo/`, `demos/` and `spec/`, in that order, if no 101 | path is given. 102 | 103 | Also notice the use of ``01_`` prefix in front of the file name. 104 | While this is not strictly necessary, QED sorts the documents, so it helps order 105 | the documents nicely, in particular when generating QED documentation ("QEDocs"). 106 | 107 | ### Utilizing Applique 108 | 109 | QED demonstrandum descriptive text is not strictly passive explanation. Using 110 | pattern matching techniques, document phrases can trigger underlying actions. 111 | These actions provide a support structure for running tests called the *applique*. 112 | 113 | Creating an applique is easy. Along with your QED scripts, to which the 114 | applique will apply, create an `applique/` directory. In this 115 | directory add Ruby scripts. When you run your demos every Ruby script in 116 | the directory will be automatically loaded. 117 | 118 | Within these applique scripts *advice* can be defined. Advice can be 119 | either *event advice*, which is simply triggered by some fixed cycle 120 | of running, such as `Before :each` or `After :all`, 121 | and *pattern advice* which are used to match against descriptive 122 | phrases in the QED demos. An example would be: 123 | 124 | When "a new round is started" do 125 | @round = [] 126 | end 127 | 128 | So that whenever the phrase "a new round is started" appears in a demo, 129 | the @round instance variable with be reset to an empty array. 130 | 131 | It is rather amazing what can be accomplished with such a system, 132 | be sure to look at QED's own demonstrandum to get a better notion of 133 | how you can put the the system to use. 134 | 135 | ### Configuration 136 | 137 | Configuration for `qed` can be placed in a `etc/qed.rb` file, or if 138 | you are using Rails, in `config/qed.rb`. Here's a generally useful 139 | example of using SimpleCov to generate a test coverage report when 140 | running your QED demos. 141 | 142 | QED.configure 'coverage' do 143 | require 'simplecov' 144 | SimpleCov.start do 145 | coverage_dir 'log/coverage' 146 | end 147 | end 148 | 149 | You can then use the profile via the `-p/--profile` option on the command line: 150 | 151 | $ qed -p coverage 152 | 153 | Or by setting the `profile` environment variable. 154 | 155 | $ profile=coverage qed 156 | 157 | QED can also use the [RC](http://rubyworks.github.com/rc) gem to handle 158 | configuration. Be sure to `gem install rc` and then add this to `.rubyrc` 159 | or `Config.rb` file of the same effect as given above. 160 | 161 | config :qed, :profile=>:coverage do 162 | require 'simplecov' 163 | SimpleCov.start do 164 | coverage_dir 'log/coverage' 165 | end 166 | end 167 | 168 | ### Generating Documentation 169 | 170 | To generate documentation from QED documents, use the +qedoc+ command. 171 | 172 | $ qedoc --output doc/qedoc --title "Example" demo/*.rdoc 173 | 174 | When documenting, QED recognizes the format by the file extension and 175 | treats it accordingly. An extension of `.qed` is treated the same 176 | as `.rdoc`. 177 | 178 | Use the `--help` options on each command to get more information 179 | on the use of these commands. 180 | 181 | 182 | ## Requirements 183 | 184 | QED depends on the following external libraries: 185 | 186 | * [BRASS](http://rubyworks.github.com/brass) - Assertions System 187 | * [ANSI](http://rubyworks.github.com/ansi) - ANSI Color Codes 188 | * [RC](http://rubyworks.github.com/rc) - Runtime Configuration 189 | * [Facets](http://rubyworks.github.com/facets) - Core Extensions 190 | 191 | These will be automatically installed when installing QED via RubyGems, 192 | if they are not already installed. 193 | 194 | Optional libraries that are generally useful with QED. 195 | 196 | * [AE](http://rubyworks.github.com/ae) - Assertions Framework 197 | 198 | Install these individually and require them in your applique to use. 199 | 200 | 201 | ## Development 202 | 203 | ### Testing 204 | 205 | QED uses itself for testing, which can be a bit tricky. But works fine for 206 | the most part. In the future we may add some addition tests via another 207 | test framework to ensure full coverage. But for now QED is proving sufficient. 208 | 209 | To run the tests, use `qed` command line tool --ideally use `$ ruby -Ilib bin/qed` 210 | to ensure the current version of QED is being used. 211 | 212 | For convenience, use `$ fire spec` to run the test specifications. To also 213 | generate a test coverage report use `$ fire spec:cov`. 214 | 215 | 216 | ## Copyrights 217 | 218 | (BSD-2-Clause license) 219 | 220 | Copyright (c) 2009 Rubyworks. All rights reserved. 221 | 222 | See LICENSE.txt for details. 223 | 224 | -------------------------------------------------------------------------------- /lib/qed/evaluator.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | require 'qed/scope' 4 | 5 | # Demonstrandum Evaluator is responsible for running demo scripts. 6 | # 7 | class Evaluator 8 | 9 | # Create new Evaluator instance and then run it. 10 | def self.run(demo, options={}) 11 | new(demo, options).run 12 | end 13 | 14 | # Setup new evaluator instance. 15 | # 16 | # @param [Demo] demo 17 | # The demo to run. 18 | # 19 | # @option options [Boolean] :applique 20 | # Is this applique code? 21 | # 22 | # @option options [Array] :observers 23 | # Objects that respond to observable interface. 24 | # Typically this is just a Reporter instance. 25 | # 26 | def initialize(demo, options={}) 27 | @demo = demo 28 | @steps = demo.steps 29 | 30 | #@settings = options[:settings] 31 | @applique = options[:applique] # BOOLEAN FLAG 32 | 33 | @observers = options[:observers].to_a 34 | @observers += applique_observers 35 | 36 | @scope = options[:scope] || Scope.new(demo) 37 | end 38 | 39 | # Collect applique all the signal-based advice and wrap their evaluation 40 | # in observable procedure calls. 41 | # 42 | def applique_observers 43 | demo = @demo 44 | demo.applique.map do |a| 45 | Proc.new do |type, *args| 46 | proc = a.__signals__[type.to_sym] 47 | @scope.instance_exec(*args, &proc) if proc 48 | end 49 | end 50 | end 51 | 52 | public 53 | 54 | # The Demo being evaluated. 55 | # 56 | # @return [Demo] 57 | attr :demo 58 | 59 | # The observers. 60 | # 61 | attr :observers 62 | 63 | # Run the demo. 64 | # 65 | def run 66 | advise!(:before_demo, @demo) 67 | begin 68 | advise!(:demo, @demo) 69 | run_steps 70 | ensure 71 | advise!(:after_demo, @demo) 72 | end 73 | end 74 | 75 | private 76 | 77 | # Interate over each step and evaluate it. 78 | # 79 | def run_steps 80 | @steps.each do |step| 81 | evaluate(step) 82 | end 83 | end 84 | 85 | # Evaluate a step. 86 | # 87 | # @macro [new] step 88 | # 89 | # @param [Step] step 90 | # The step being evaluated. 91 | # 92 | # @return nothing 93 | def evaluate(step) 94 | advise!(:before_step, step) 95 | advise!(:step, step) 96 | 97 | evaluate_links(step) unless step.heading? 98 | 99 | if step.assertive? && !@applique 100 | evaluate_test(step) 101 | else 102 | evaluate_applique(step) 103 | end 104 | 105 | advise!(:after_step, step) 106 | end 107 | 108 | # TODO: We may deprecate link helpers --it's probably not a good idea 109 | # to have per-demo rules any way. 110 | 111 | # TODO: Not sure how to handle loading links in --comment runner mode. 112 | 113 | # TODO: Should scope be reused by imported demo ? 114 | 115 | # If there are embedded links in the step description than extract 116 | # them and load them in. 117 | # 118 | # @macro step 119 | def evaluate_links(step) 120 | step.text.scan(/(?:\(|\[)qed:\/\/(.*?)(?:\)|\])/) do |match| 121 | file = $1 122 | # relative to demo demo 123 | if File.exist?(File.join(@demo.directory,file)) 124 | file = File.join(@demo.directory,file) 125 | end 126 | 127 | advise!(:before_import, file) 128 | begin 129 | advise!(:import, file) 130 | case File.extname(file) 131 | when '.rb' 132 | Kernel.eval(File.read(file), @scope.__binding__, file) 133 | else 134 | demo = Demo.new(file) 135 | Evaluator.new(demo, :scope=>@scope).run 136 | end 137 | ensure 138 | advise!(:after_import, file) 139 | end 140 | end 141 | end 142 | 143 | # Evaluate step at the *applique level*. This means the execution 144 | # of code and even matcher evaluations will not be captured by a 145 | # rescue clause. 146 | # 147 | # @macro step 148 | def evaluate_applique(step) 149 | advise!(:before_applique, step) 150 | begin 151 | advise!(:applique, step) 152 | evaluate_matchers(step) 153 | evaluate_example(step) 154 | ensure 155 | advise!(:after_applique, step) 156 | end 157 | end 158 | 159 | # Exceptions to always raise regardless. 160 | FORCED_EXCEPTIONS = [NoMemoryError, SignalException, Interrupt] #, SystemExit] 161 | 162 | # Evaluate the step's matchers and code sample, wrapped in a begin-rescue 163 | # clause. 164 | # 165 | # @macro step 166 | def evaluate_test(step) 167 | advise!(:before_test, step) 168 | begin 169 | advise!(:test, step) # name ? 170 | evaluate_matchers(step) 171 | evaluate_example(step) 172 | rescue *FORCED_EXCEPTIONS 173 | raise 174 | rescue SystemExit # TODO: why pass on SystemExit ? 175 | advise!(:pass, step) 176 | #rescue Assertion => exception 177 | # advise!(:fail, step, exception) 178 | rescue Exception => exception 179 | if exception.assertion? 180 | advise!(:fail, step, exception) 181 | else 182 | advise!(:error, step, exception) 183 | end 184 | else 185 | advise!(:pass, step) 186 | ensure 187 | advise!(:after_test, step) 188 | end 189 | end 190 | 191 | # Evaluate the step's example in the demo's context, if the example 192 | # is source code. 193 | # 194 | # @macro step 195 | def evaluate_example(step) 196 | @scope.evaluate(step.code, step.file, step.lineno) if step.code? 197 | end 198 | 199 | # Search the step's description for applique matches and 200 | # evaluate them. 201 | # 202 | # @macro step 203 | def evaluate_matchers(step) 204 | match = step.text 205 | 206 | @demo.applique.each do |app| 207 | app.__matchers__.each do |(patterns, proc)| 208 | compare = match 209 | matched = true 210 | params = [] 211 | patterns.each do |pattern| 212 | case pattern 213 | when Regexp 214 | regex = pattern 215 | else 216 | regex = match_string_to_regexp(pattern) 217 | end 218 | if md = regex.match(compare) 219 | advise!(:match, step, md) # ADVISE ! 220 | params.concat(md[1..-1]) 221 | compare = md.post_match 222 | else 223 | matched = false 224 | break 225 | end 226 | end 227 | if matched 228 | #args = [params, arguments].reject{|e| e == []} # use single argument for params in 3.0? 229 | args = params 230 | args = args + [step.sample_text] if step.data? 231 | args = proc.arity < 0 ? args : args[0,proc.arity] 232 | 233 | #@demo.scope 234 | @scope.instance_exec(*args, &proc) #proc.call(*args) 235 | end 236 | end 237 | end 238 | end 239 | 240 | # 241 | SPLIT_PATTERNS = [ /(\(\(.*?\)\)(?!\)))/, /(\/\(.*?\)\/)/, /(\/\?.*?\/)/ ] 242 | 243 | # 244 | SPLIT_PATTERN = Regexp.new(SPLIT_PATTERNS.join('|')) 245 | 246 | # Convert matching string into a regular expression. If the string 247 | # contains double parenthesis, such as ((.*?)), then the text within 248 | # them is treated as in regular expression and kept verbatium. 249 | # 250 | def match_string_to_regexp(str) 251 | re = nil 252 | str = str.split(SPLIT_PATTERN).map do |x| 253 | case x 254 | when /\A\(\((.*?)\)\)(?!\))/ 255 | $1 256 | when /\A\/(\(.*?\))\// 257 | $1 258 | when /\A\/(\?.*?)\// 259 | "(#{$1})" 260 | else 261 | Regexp.escape(x) 262 | end 263 | end.join 264 | 265 | str = str.gsub(/\\\s+/, '\s+') # Replace space with variable space. 266 | 267 | Regexp.new(str, Regexp::IGNORECASE) 268 | end 269 | 270 | =begin 271 | # The following code works as well, and can provide a MatchData 272 | # object instead of just matching params, but I call YAGNI on that 273 | # and it has two benefits. 1) the above code is faster, and 2) 274 | # using params allows |(name1, name2)| in rule blocks. 275 | 276 | # 277 | def evaluate_matchers(step) 278 | match = step.text 279 | args = step.arguments 280 | @demo.applique.each do |a| 281 | matchers = a.__matchers__ 282 | matchers.each do |(patterns, proc)| 283 | re = build_matcher_regexp(*patterns) 284 | if md = re.match(match) 285 | #params = [step.text.strip] + params 286 | #proc.call(*params) 287 | @demo.scope.instance_exec(md, *args, &proc) 288 | end 289 | end 290 | end 291 | end 292 | 293 | # 294 | def build_matcher_regexp(*patterns) 295 | parts = [] 296 | patterns.each do |pattern| 297 | case pattern 298 | when Regexp 299 | parts << pattern 300 | else 301 | parts << match_string_to_regexp(pattern) 302 | end 303 | end 304 | Regexp.new(parts.join('.*?'), Regexp::MULTILINE) 305 | end 306 | =end 307 | 308 | # TODO: pass demo to advice? 309 | 310 | # Dispatch an advice event to observers. 311 | # 312 | # @param [Symbol] signal 313 | # The name of the dispatch. 314 | # 315 | # @param [Array] args 316 | # Any arguments to send along witht =the signal to the observers. 317 | # 318 | # @return nothing 319 | def advise!(signal, *args) 320 | @observers.each{ |o| o.call(signal.to_sym, *args) } 321 | end 322 | 323 | end 324 | 325 | end 326 | -------------------------------------------------------------------------------- /work/trash/grammar/legacy/assert.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | 3 | module Grammar #:nodoc: 4 | 5 | module Legacy #:nodoc: 6 | 7 | # = Test::Unit Legacy Assertions 8 | # 9 | # This module provides a compatibility layer for Test::Unit. 10 | # This is an optional module and is intended for providing 11 | # an easier transition from Test::Unit::TestCase to Quarry 12 | # Specifications. 13 | # 14 | # Note that two methods are not provided, +#assert_nothing_raised+, 15 | # and +#assert_nothing_thrown+. 16 | # 17 | module Assertions 18 | 19 | # Private method upon which all of the legacy assertions are based 20 | # (except for #assert itself). 21 | # 22 | def __assert__(test, msg=nil) 23 | msg = "failed assertion (no message given)" unless msg 24 | raise Assertion.new(msg, caller[1..-1]) unless test 25 | end 26 | 27 | private :__assert__ 28 | 29 | # The assertion upon which all other assertions are based. 30 | # 31 | # assert [1, 2].include?(5) 32 | # 33 | def assert(test=nil, msg=nil) 34 | if test 35 | msg = "failed assertion (no message given)" unless msg 36 | raise Assertion.new(msg, caller) unless test 37 | else 38 | Expectation.new(self, :backtrace=>caller) 39 | end 40 | end 41 | 42 | # Passes if the block yields true. 43 | # 44 | # assert_block "Couldn't do the thing" do 45 | # do_the_thing 46 | # end 47 | # 48 | def assert_block(msg=nil) # :yields: 49 | test = ! yield 50 | msg = "assertion failed" unless msg 51 | __assert__(test, msg) 52 | end 53 | 54 | # Passes if expected == +actual. 55 | # 56 | # Note that the ordering of arguments is important, 57 | # since a helpful error message is generated when this 58 | # one fails that tells you the values of expected and actual. 59 | # 60 | # assert_equal 'MY STRING', 'my string'.upcase 61 | # 62 | def assert_equal(exp, act, msg=nil) 63 | test = (exp == act) 64 | msg = "Expected #{act.inspect} to be equal to #{exp.inspect}" unless msg 65 | __assert__(test, msg) 66 | end 67 | 68 | # Passes if expected_float and actual_float are equal within delta tolerance. 69 | # 70 | # assert_in_delta 0.05, (50000.0 / 10**6), 0.00001 71 | # 72 | def assert_in_delta(exp, act, delta, msg=nil) 73 | test = (exp.to_f - act.to_f).abs <= delta.to_f 74 | msg = "Expected #{exp} to be within #{delta} of #{act}" unless msg 75 | __assert__(test, msg) 76 | end 77 | 78 | # Passes if object .instance_of? klass 79 | # 80 | # assert_instance_of String, 'foo' 81 | # 82 | def assert_instance_of(cls, obj, msg=nil) 83 | test = (cls === obj) 84 | msg = "Expected #{obj} to be a #{cls}" unless msg 85 | __assert__(test, msg) 86 | end 87 | 88 | # Passes if object .kind_of? klass 89 | # 90 | # assert_kind_of Object, 'foo' 91 | # 92 | def assert_kind_of(cls, obj, msg=nil) 93 | test = obj.kind_of?(cls) 94 | msg = "Expected #{obj.inspect} to be a kind of #{cls}" unless msg 95 | __assert__(test, msg) 96 | end 97 | 98 | # Passes if string =~ pattern. 99 | # 100 | # assert_match(/\d+/, 'five, 6, seven') 101 | # 102 | def assert_match(exp, act, msg=nil) 103 | test = (act =~ exp) 104 | msg = "Expected #{act.inspect} to match #{exp.inspect}" unless msg 105 | __assert__(test, msg) 106 | end 107 | 108 | # Passes if object is nil. 109 | # 110 | # assert_nil [1, 2].uniq! 111 | # 112 | def assert_nil(obj, msg=nil) 113 | test = obj.nil? 114 | msg = "Expected #{obj.inspect} to be nil" unless msg 115 | __assert__(test, msg) 116 | end 117 | 118 | # Passes if regexp !~ string 119 | # 120 | # assert_no_match(/two/, 'one 2 three') 121 | # 122 | def assert_no_match(exp, act, msg=nil) 123 | test = (act !~ exp) 124 | msg = "Expected #{act.inspect} to match #{exp.inspect}" unless msg 125 | __assert__(test, msg) 126 | end 127 | 128 | # Passes if expected != actual 129 | # 130 | # assert_not_equal 'some string', 5 131 | # 132 | def assert_not_equal(exp, act, msg=nil) 133 | test = (exp != act) 134 | msg = "Expected #{act.inspect} to not be equal to #{exp.inspect}" unless msg 135 | __assert__(test, msg) 136 | end 137 | 138 | # Passes if ! object .nil? 139 | # 140 | # assert_not_nil '1 two 3'.sub!(/two/, '2') 141 | # 142 | def assert_not_nil(obj, msg=nil) 143 | test = ! obj.nil? 144 | msg = "Expected #{obj.inspect} to not be nil" unless msg 145 | __assert__(test, msg) 146 | end 147 | 148 | # Passes if ! actual .equal? expected 149 | # 150 | # assert_not_same Object.new, Object.new 151 | # 152 | def assert_not_same(exp, act, msg=nil) 153 | test = ! exp.equal?(act) 154 | msg = "Expected #{act.inspect} to not be the same as #{exp.inspect}" unless msg 155 | __assert__(test, msg) 156 | end 157 | 158 | # Compares the +object1+ with +object2+ using operator. 159 | # 160 | # Passes if object1.send(operator, object2) is true. 161 | # 162 | # assert_operator 5, :>=, 4 163 | # 164 | def assert_operator(o1, op, o2, msg="") 165 | test = o1.__send__(op, o2) 166 | msg = "Expected #{o1}.#{op}(#{o2}) to be true" unless msg 167 | __assert__(test, msg) 168 | end 169 | 170 | # Passes if the block raises one of the given exceptions. 171 | # 172 | # assert_raise RuntimeError, LoadError do 173 | # raise 'Boom!!!' 174 | # end 175 | # 176 | def assert_raises(*args) 177 | if msg = (Module === args.last ? nil : args.pop) 178 | begin 179 | yield 180 | msg = "Expected #{exp} to be raised" unless msg 181 | __assert__(false, msg) 182 | rescue Exception => e 183 | test = (exp === e) 184 | msg = "Expected #{exp} to be raised, but got #{e.class}" unless msg 185 | __assert__(test, msg) 186 | return e 187 | end 188 | end 189 | 190 | alias_method :assert_raise, :assert_raises 191 | 192 | # Provides a way to assert that a procedure 193 | # does not raise an exception. 194 | # 195 | # refute_raises(StandardError){ raise } 196 | # 197 | #def assert_raises!(exception, &block) 198 | # begin 199 | # block.call(*a) 200 | # rescue exception 201 | # raise Assertion 202 | # end 203 | #end 204 | #alias_method :refute_raises, :assert_raises! 205 | 206 | # Passes if +object+ respond_to? +method+. 207 | # 208 | # assert_respond_to 'bugbear', :slice 209 | # 210 | def assert_respond_to(obj, meth, msg=nil) 211 | msg = "Expected #{obj} (#{obj.class}) to respond to ##{meth}" unless msg 212 | #flip = (Symbol === obj) && ! (Symbol === meth) # HACK for specs 213 | #obj, meth = meth, obj if flip 214 | test = obj.respond_to?(meth) 215 | __assert__(test, msg) 216 | end 217 | 218 | # Passes if +actual+ .equal? +expected+ (i.e. they are the same instance). 219 | # 220 | # o = Object.new 221 | # assert_same(o, o) 222 | # 223 | def assert_same(exp, act, msg=nil) 224 | msg = "Expected #{act.inspect} to be the same as #{exp.inspect}" unless msg 225 | test = exp.equal?(act) 226 | __assert__(test, msg) 227 | end 228 | 229 | # Passes if the method send returns a true value. 230 | # The parameter +send_array+ is composed of: 231 | # 232 | # * A receiver 233 | # * A method 234 | # * Arguments to the method 235 | # 236 | # Example: 237 | # 238 | # assert_send [[1, 2], :include?, 4] 239 | # 240 | def assert_send(send_array, msg=nil) 241 | r, m, *args = *send_array 242 | test = r.__send__(m, *args) 243 | msg = "Expected #{r}.#{m}(*#{args.inspect}) to return true" unless msg 244 | __assert__(test, msg) 245 | end 246 | 247 | # Passes if the block throws expected_symbol 248 | # 249 | # assert_throws :done do 250 | # throw :done 251 | # end 252 | # 253 | def assert_throws(sym, msg=nil) 254 | msg = "Expected #{sym} to have been thrown" unless msg 255 | test = true 256 | catch(sym) do 257 | begin 258 | yield 259 | rescue ArgumentError => e # 1.9 exception 260 | default += ", not #{e.message.split(/ /).last}" 261 | rescue NameError => e # 1.8 exception 262 | default += ", not #{e.name.inspect}" 263 | end 264 | test = false 265 | end 266 | __assert__(test, msg) 267 | end 268 | 269 | # Flunk always fails. 270 | # 271 | # flunk 'Not done testing yet.' 272 | # 273 | def flunk(msg=nil) 274 | __assert__(false, msg) 275 | end 276 | 277 | end #module Assertions 278 | 279 | end #module Legacy 280 | 281 | end #module Grammar 282 | 283 | # This could be in Object, but since they will only be needed in 284 | # the context of a, well, Context... 285 | # 286 | class Context #:nodoc: 287 | include Grammar::Legacy::Assertions 288 | end 289 | 290 | end #module Quarry 291 | 292 | -------------------------------------------------------------------------------- /lib/qed/document/template.rhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= title %> 4 | 5 | 6 | 7 | 8 | 104 | 105 | <% if css %> 106 | 107 | <% end %> 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 136 | 137 |
138 | 149 | 150 |
151 | <%= spec %> 152 |
153 |
154 | 155 |


156 | 157 | 158 | 159 | 160 | 161 | 162 | 226 | 227 | 239 | 240 | -------------------------------------------------------------------------------- /lib/qed/reporter/abstract.rb: -------------------------------------------------------------------------------- 1 | module QED 2 | module Reporter 3 | 4 | # TODO: What was this for? 5 | #require 'facets/string' 6 | 7 | begin 8 | require 'ansi/core' 9 | rescue LoadError 10 | require 'ansi/code' 11 | end 12 | 13 | # = Reporter Absract Base Class 14 | # 15 | # Serves as the base class for all other output formats. 16 | class Abstract 17 | 18 | # Does the system support INFO signal? 19 | INFO_SIGNAL = Signal.list['INFO'] 20 | 21 | # 22 | attr :session 23 | 24 | # 25 | attr :io 26 | 27 | # 28 | attr :record 29 | 30 | # TODO: pass session into initialize 31 | def initialize(options={}) 32 | @io = options[:io] || STDOUT 33 | @trace = options[:trace] 34 | 35 | @record = { 36 | :demo => [], 37 | :step => [], 38 | :omit => [], 39 | :pass => [], 40 | :fail => [], 41 | :error => [] 42 | } 43 | 44 | #@demos = 0 45 | #@steps = 0 46 | #@omit = [] 47 | #@pass = [] 48 | #@fail = [] 49 | #@error = [] 50 | 51 | @source = {} 52 | end 53 | 54 | def demos ; @record[:demo] ; end 55 | def steps ; @record[:step] ; end 56 | def omits ; @record[:omit] ; end 57 | def passes ; @record[:pass] ; end 58 | def errors ; @record[:error] ; end 59 | def fails ; @record[:fail] ; end 60 | 61 | # 62 | def trace? 63 | @trace 64 | end 65 | 66 | # 67 | def success? 68 | record[:error].size + record[:fail].size == 0 69 | end 70 | 71 | # 72 | def call(type, *args) 73 | __send__("count_#{type}", *args) if respond_to?("count_#{type}") 74 | __send__(type, *args) if respond_to?(type) 75 | end 76 | 77 | def self.When(type, &block) 78 | #raise ArgumentError unless %w{session demo demonstration step}.include?(type.to_s) 79 | #type = :demonstration if type.to_s == 'demo' 80 | define_method(type, &block) 81 | end 82 | 83 | def self.Before(type, &block) 84 | # raise ArgumentError unless %w{session demo demonstration step}.include?(type.to_s) 85 | # type = :demonstration if type.to_s == 'demo' 86 | define_method("before_#{type}", &block) 87 | end 88 | 89 | def self.After(type, &block) 90 | # raise ArgumentError unless %w{session demo demonstration step pass fail error}.include?(type.to_s) 91 | # type = :demonstration if type.to_s == 'demo' 92 | define_method("after_#{type}", &block) 93 | end 94 | 95 | # 96 | #def Before(type, target, *args) 97 | # type = :demonstration if type.to_s == 'demo' 98 | # __send__("before_#{type}", target, *args) 99 | #end 100 | 101 | # 102 | #def After(type, target, *args) 103 | # type = :demonstration if type.to_s == 'demo' 104 | # __send__("after_#{type}", target, *args) 105 | #end 106 | 107 | def count_demo(demo) 108 | @record[:demo] << demo 109 | end 110 | 111 | def count_step(step) 112 | @record[:step] << step 113 | end 114 | 115 | #def count_eval(step) 116 | # @record[:eval] << step 117 | #end 118 | 119 | def count_pass(step) 120 | @record[:pass] << step 121 | end 122 | 123 | def count_fail(step, exception) 124 | @record[:fail] << [step, exception] 125 | end 126 | 127 | def count_error(step, exception) 128 | @record[:error] << [step, exception] 129 | end 130 | 131 | 132 | # At the start of a session, before running any demonstrations. 133 | def before_session(session) 134 | @session = session 135 | @start_time = Time.now 136 | end 137 | 138 | # Beginning of a demonstration. 139 | def before_demo(demo) #demo(demo) 140 | #demos << demo 141 | end 142 | 143 | # 144 | def before_import(file) 145 | end 146 | 147 | # 148 | def before_step(step) 149 | end 150 | 151 | # 152 | def before_proc(step) 153 | end 154 | 155 | # 156 | def before_eval(step) 157 | end 158 | 159 | # Before running a step that is omitted. 160 | #def before_omit(step) 161 | # @omit << step 162 | #end 163 | 164 | # Reight before demo. 165 | def demo(demo) 166 | end 167 | 168 | # Right before import. 169 | def import(file) 170 | end 171 | 172 | # Right before rule section. 173 | def rule(step) 174 | end 175 | 176 | # Right before text section. 177 | def step(step) #show text ? 178 | end 179 | 180 | # Right before evaluation. 181 | def proc(step) 182 | end 183 | 184 | # Right before evaluation. 185 | def eval(step) 186 | end 187 | 188 | # Right before evaluation. 189 | #def code(step) 190 | #end 191 | 192 | # After running a step that passed. 193 | def pass(step) 194 | #@pass << step 195 | end 196 | 197 | # After running a step that failed. 198 | def fail(step, assertion) 199 | ## @fail << [step, assertion] 200 | end 201 | 202 | # After running a step that raised an error. 203 | def error(step, exception) 204 | raise exception if $DEBUG # TODO: do we really want to do it like this? 205 | ## @error << [step, exception] 206 | end 207 | 208 | # 209 | def after_import(file) 210 | end 211 | 212 | # 213 | def after_eval(step) 214 | end 215 | 216 | # 217 | def after_proc(step) 218 | end 219 | 220 | # 221 | def after_step(step) 222 | end 223 | 224 | # End of a demonstration. 225 | def after_demo(demo) #demo(demo) 226 | end 227 | 228 | # After running all demonstrations. This is the place 229 | # to output a summary of the session, if applicable. 230 | def after_session(session) 231 | end 232 | 233 | private 234 | 235 | def print_time 236 | io.puts "\nFinished in %.5f seconds.\n\n" % [Time.now - @start_time] 237 | end 238 | 239 | def print_tally 240 | #assert_count = AE::Assertor.counts[:total] 241 | #assert_fails = AE::Assertor.counts[:fail] 242 | #assert_delta = assert_count - assert_fails 243 | 244 | mask = "%s demos, %s steps: %s failures, %s errors (%s/%s assertions)" 245 | #vars = [demos.size, steps.size, fails.size, errors.size, assert_delta, assert_count] #, @pass.size ] 246 | 247 | io.puts mask % get_tally 248 | end 249 | 250 | # 251 | def get_tally 252 | assert_count = $ASSERTION_COUNTS[:total] 253 | assert_fails = $ASSERTION_COUNTS[:fail] 254 | assert_delta = assert_count - assert_fails 255 | 256 | vars = [demos.size, steps.size, fails.size, errors.size, assert_delta, assert_count] #, @pass.size ] 257 | 258 | vars 259 | end 260 | 261 | # TODO: Use global standard for backtrace exclusions. 262 | INTERNALS = /(lib|bin)[\\\/](qed|ae)/ 263 | 264 | # 265 | def sane_backtrace(exception) 266 | if trace_count 267 | clean_backtrace(*exception.backtrace[0, trace_count]) 268 | else 269 | clean_backtrace(*exception.backtrace) 270 | end 271 | end 272 | 273 | # 274 | def clean_backtrace(*btrace) 275 | stack = if $DEBUG 276 | btrace 277 | else 278 | btrace.reject{ |bt| bt =~ INTERNALS } 279 | end 280 | stack.map do |bt| 281 | bt.chomp(":in \`__binding__'") 282 | end 283 | end 284 | 285 | =begin 286 | # Clean the backtrace of any reference to ko/ paths and code. 287 | def clean_backtrace(backtrace) 288 | trace = backtrace.reject{ |bt| bt =~ INTERNALS } 289 | trace.map do |bt| 290 | if i = bt.index(':in') 291 | bt[0...i] 292 | else 293 | bt 294 | end 295 | end 296 | end 297 | =end 298 | 299 | # Produce a pretty code snippet given an exception. 300 | # 301 | # @param exception [Exception, String] 302 | # An exception or backtrace. 303 | # 304 | # @param radius [Integer] 305 | # The number of surrounding lines to show. 306 | # 307 | # @return [String] pretty code snippet 308 | def code_snippet(exception, radius=2) 309 | radius = radius.to_i 310 | 311 | file, lineno = file_and_line(exception) 312 | 313 | return nil if file.empty? 314 | return nil if file == '(eval)' 315 | 316 | source = source(file) 317 | 318 | region = [lineno - radius, 1].max .. 319 | [lineno + radius, source.length].min 320 | 321 | # ensure proper alignment by zero-padding line numbers 322 | format = " %2s %0#{region.last.to_s.length}d %s" 323 | 324 | pretty = region.map do |n| 325 | format % [('=>' if n == lineno), n, source[n-1].chomp] 326 | end #.unshift "[#{region.inspect}] in #{source_file}" 327 | 328 | pretty 329 | end 330 | 331 | # Return a structure code snippet in an array of lineno=>line 332 | # hash elements. 333 | # 334 | # @param exception [Exception, String] 335 | # An exception or backtrace. 336 | # 337 | # @param radius [Integer] 338 | # The number of surrounding lines to show. 339 | # 340 | # @return [Hash] structured code snippet 341 | def structured_code_snippet(exception, radius=2) 342 | radius = radius.to_i 343 | 344 | file, lineno = file_and_line(exception) 345 | 346 | return {} if file.empty? 347 | 348 | source = source(file) 349 | 350 | region = [lineno - radius, 1].max .. 351 | [lineno + radius, source.length].min 352 | 353 | region.map do |n| 354 | {n => source[n-1].chomp} 355 | end 356 | end 357 | 358 | # Cache the source code of a file. 359 | # 360 | # @param file [String] full pathname to file 361 | # 362 | # @return [String] source code 363 | def source(file) 364 | @source[file] ||= ( 365 | if File.exist?(file) 366 | File.readlines(file) 367 | else 368 | '' 369 | end 370 | ) 371 | end 372 | 373 | # @param exception [Exception,Array,String] 374 | # An exception or backtrace. 375 | # 376 | #-- 377 | # TODO: Show more of the file name than just the basename. 378 | #++ 379 | def file_and_line(exception) 380 | backtrace = case exception 381 | when Exception 382 | exception.backtrace.reject{ |bt| bt =~ INTERNALS }.first 383 | when Array 384 | exception.first 385 | else 386 | exception 387 | end 388 | 389 | backtrace =~ /(.+?):(\d+(?=:|\z))/ or return "" 390 | 391 | file, lineno = $1, $2.to_i 392 | 393 | return file, lineno 394 | 395 | #i = backtrace.rindex(':in') 396 | #line = i ? line[0...i] : line 397 | #relative_file(line) 398 | end 399 | 400 | # Same as file_and_line, exception return file path is relative. 401 | def file_line(exception) 402 | file, lineno = file_and_line(exception) 403 | return relative_file(file), lineno 404 | end 405 | 406 | # Default trace count. This is the number of backtrace lines that 407 | # will be provided on errors and failed assertions, unless otherwise 408 | # overridden with ENV['trace']. 409 | DEFAULT_TRACE_COUNT = 3 410 | 411 | # Looks at ENV['trace'] to determine how much trace output to provide. 412 | # If it is not set, or set to`false` or `off`, then the default trace count 413 | # is used. If set to `0`, `true`, 'on' or 'all' then aa complete trace dump 414 | # is provided. Otherwise the value is converted to an integer and that many 415 | # line of trace is given. 416 | # 417 | # @return [Integer, nil] trace count 418 | def trace_count 419 | cnt = ENV['trace'] 420 | case cnt 421 | when nil, 'false', 'off' 422 | DEFAULT_TRACE_COUNT 423 | when 0, 'all', 'true', 'on' 424 | nil 425 | else 426 | Integer(cnt) 427 | end 428 | end 429 | 430 | # 431 | def relative_file(file) 432 | pwd = Dir.pwd 433 | idx = (0...pwd.size).find do |i| 434 | file[i,1] != pwd[i,1] 435 | end 436 | idx ||= 1 437 | file[(idx-1)..-1] 438 | end 439 | 440 | # 441 | def localize_file(file) 442 | j = 0 443 | [file.to_s.size, Dir.pwd.size].max.times do |i| 444 | if Dir.pwd[i,1] != file[i,1] 445 | break j = i 446 | end 447 | end 448 | file[j..-1] 449 | end 450 | 451 | end 452 | 453 | end 454 | end 455 | 456 | --------------------------------------------------------------------------------