qed 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +622 -344
- data/DIARY.rdoc +117 -0
- data/HISTORY +36 -0
- data/PROFILE +16 -0
- data/README.rdoc +69 -36
- data/REQUIRE +9 -0
- data/ROADMAP +12 -0
- data/VERSION +5 -0
- data/demo/01_demos.rdoc +56 -0
- data/demo/02_advice.rdoc +158 -0
- data/demo/03_helpers.rdoc +42 -0
- data/demo/04_fixtures.rdoc +29 -0
- data/demo/05_quote.rdoc +24 -0
- data/demo/07_toplevel.rdoc +42 -0
- data/demo/08_cross_script.rdoc +27 -0
- data/demo/09_cross_script.rdoc +27 -0
- data/demo/10_constant_lookup.rdoc +16 -0
- data/demo/applique/constant.rb +2 -0
- data/demo/applique/env.rb +5 -0
- data/demo/applique/fileutils.rb +1 -0
- data/demo/applique/markup.rb +10 -0
- data/demo/applique/quote.rb +4 -0
- data/demo/applique/toplevel.rb +15 -0
- data/demo/fixtures/data.txt +1 -0
- data/demo/fixtures/table.yml +5 -0
- data/demo/helpers/advice.rb +40 -0
- data/demo/helpers/sample.rb +4 -0
- data/demo/helpers/toplevel.rb +6 -0
- data/eg/hello_world.rdoc +15 -0
- data/{demo/error.rdoc → eg/view_error.rdoc} +0 -0
- data/{demo → eg}/website.rdoc +0 -0
- data/lib/qed.rb +20 -1
- data/lib/qed/advice.rb +4 -30
- data/lib/qed/advice/events.rb +6 -3
- data/lib/qed/advice/patterns.rb +37 -19
- data/lib/qed/applique.rb +85 -0
- data/lib/qed/command.rb +3 -5
- data/lib/qed/evaluator.rb +52 -56
- data/lib/qed/package.yml +5 -0
- data/lib/qed/parser.rb +149 -0
- data/lib/qed/profile.yml +16 -0
- data/lib/qed/reporter/{base.rb → abstract.rb} +17 -19
- data/lib/qed/reporter/bullet.rb +14 -16
- data/lib/qed/reporter/dotprogress.rb +7 -6
- data/lib/qed/reporter/html.rb +21 -3
- data/lib/qed/reporter/verbatim.rb +28 -26
- data/lib/qed/scope.rb +98 -82
- data/lib/qed/script.rb +21 -69
- data/lib/qed/session.rb +44 -3
- data/script/qedoc +2 -0
- data/script/test +2 -0
- metadata +74 -28
- data/doc/qedoc/index.html +0 -515
- data/doc/qedoc/jquery.js +0 -19
- data/meta/authors +0 -1
- data/meta/created +0 -1
- data/meta/description +0 -2
- data/meta/homepage +0 -1
- data/meta/name +0 -1
- data/meta/released +0 -1
- data/meta/repository +0 -1
- data/meta/requires +0 -5
- data/meta/ruby +0 -2
- data/meta/suite +0 -1
- data/meta/summary +0 -1
- data/meta/title +0 -1
- data/meta/version +0 -1
data/lib/qed/advice/patterns.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module QED
|
2
2
|
|
3
|
-
# =
|
3
|
+
# = Pattern Advice (When)
|
4
4
|
#
|
5
|
-
# This class
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
5
|
+
# This class encapsulates "When" advice on plain text.
|
6
|
+
#
|
7
|
+
# Matches are evaluated in Scope context, via #instance_exec,
|
8
|
+
# so that the advice methods will have access to the same
|
9
|
+
# scope as the demonstrandum themselves.
|
9
10
|
#
|
10
11
|
class Patterns
|
11
12
|
|
@@ -16,33 +17,50 @@ module QED
|
|
16
17
|
end
|
17
18
|
|
18
19
|
#
|
19
|
-
def add(
|
20
|
-
@when << [
|
20
|
+
def add(patterns, &procedure)
|
21
|
+
@when << [patterns, procedure]
|
21
22
|
end
|
22
23
|
|
23
24
|
#
|
24
|
-
def call(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
def call(scope, section)
|
26
|
+
match = section.text
|
27
|
+
args = section.args
|
28
|
+
|
29
|
+
@when.each do |(patterns, proc)|
|
30
|
+
compare = match
|
31
|
+
matched = true
|
32
|
+
params = []
|
33
|
+
patterns.each do |pattern|
|
34
|
+
case pattern
|
35
|
+
when Regexp
|
36
|
+
regex = pattern
|
37
|
+
else
|
38
|
+
regex = when_string_to_regexp(pattern)
|
39
|
+
end
|
40
|
+
if md = regex.match(compare)
|
41
|
+
params.concat(md[1..-1])
|
42
|
+
compare = md.post_match
|
43
|
+
else
|
44
|
+
matched = false
|
45
|
+
break
|
46
|
+
end
|
31
47
|
end
|
32
|
-
if
|
33
|
-
|
48
|
+
if matched
|
49
|
+
params += args
|
50
|
+
#proc.call(*params)
|
51
|
+
scope.instance_exec(*params, &proc)
|
34
52
|
end
|
35
53
|
end
|
36
54
|
end
|
37
55
|
|
38
56
|
private
|
39
57
|
|
40
|
-
#
|
58
|
+
# TODO: Now that we can use multi-patterns, we might not need this any more.
|
41
59
|
def when_string_to_regexp(str)
|
42
60
|
str = str.split(/(\(\(.*?\)\))(?!\))/).map{ |x|
|
43
|
-
x =~ /\A\(\((.*)\)\)\
|
61
|
+
x =~ /\A\(\((.*)\)\)\Z/ ? $1 : Regexp.escape(x)
|
44
62
|
}.join
|
45
|
-
str = str.gsub(
|
63
|
+
str = str.gsub(/\\\s+/, '\s+')
|
46
64
|
Regexp.new(str, Regexp::IGNORECASE)
|
47
65
|
|
48
66
|
#rexps = []
|
data/lib/qed/applique.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'qed/advice'
|
2
|
+
|
3
|
+
module QED
|
4
|
+
|
5
|
+
# The Applique is the environment of libraries required by
|
6
|
+
# and the rules to apply to demonstrandum. The applique is
|
7
|
+
# defined by a set of scripts located in the +applique+
|
8
|
+
# directory of the upper-most test directory relative to
|
9
|
+
# the tests run and below the root of a project. All
|
10
|
+
# applique scripts are loaded at the start of a test
|
11
|
+
# session. Thus all demos belong to one and only one
|
12
|
+
# applique, and all the scripts in an applique must be
|
13
|
+
# compatible/consistant. For two demos to have separate
|
14
|
+
# applique they must be kept in separate directores.
|
15
|
+
|
16
|
+
class Applique < Module
|
17
|
+
|
18
|
+
#
|
19
|
+
def initialize
|
20
|
+
super()
|
21
|
+
extend self
|
22
|
+
@__advice__ = Advice.new
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
def initialize_copy(other)
|
27
|
+
@__advice__ = other.__advice__.dup
|
28
|
+
end
|
29
|
+
|
30
|
+
# Redirect missing constants to Object class
|
31
|
+
# to simulate TOPLEVEL.
|
32
|
+
#
|
33
|
+
# TODO: Clean backtrace when constant is not found.
|
34
|
+
def const_missing(name)
|
35
|
+
Object.const_get(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
def __advice__
|
40
|
+
@__advice__
|
41
|
+
end
|
42
|
+
|
43
|
+
# Because patterns are mathced against HTML documents
|
44
|
+
# HTML special charaters +<+, +>+ and +&+ should not be
|
45
|
+
# used.
|
46
|
+
def When(*patterns, &procedure)
|
47
|
+
if patterns.size == 1 && Symbol === patterns.first
|
48
|
+
__advice__.events.add(:"#{patterns.first}", &procedure)
|
49
|
+
else
|
50
|
+
__advice__.patterns.add(patterns, &procedure)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Before advice.
|
55
|
+
def Before(type=:code, &procedure)
|
56
|
+
__advice__.events.add(:"before_#{type}", &procedure)
|
57
|
+
end
|
58
|
+
|
59
|
+
# After advice.
|
60
|
+
def After(type=:code, &procedure)
|
61
|
+
__advice__.events.add(:"after_#{type}", &procedure)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Code match-and-transform procedure.
|
65
|
+
#
|
66
|
+
# This is useful to transform human readable code examples
|
67
|
+
# into proper exectuable code. For example, say you want to
|
68
|
+
# run shell code, but want to make if look like typical
|
69
|
+
# shelle examples:
|
70
|
+
#
|
71
|
+
# $ cp fixture/a.rb fixture/b.rb
|
72
|
+
#
|
73
|
+
# You can use a transform to convert lines starting with '$'
|
74
|
+
# into executable Ruby using #system.
|
75
|
+
#
|
76
|
+
# system('cp fixture/a.rb fixture/b.rb')
|
77
|
+
#
|
78
|
+
#def Transform(pattern=nil, &procedure)
|
79
|
+
#
|
80
|
+
#end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
data/lib/qed/command.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'qed'
|
4
4
|
require 'optparse'
|
5
5
|
require 'shellwords'
|
6
|
-
require 'tilt'
|
7
6
|
|
8
7
|
module QED
|
9
8
|
|
@@ -17,7 +16,7 @@ module QED
|
|
17
16
|
# Default location of demonstrations if no
|
18
17
|
# specific files or locations given. This
|
19
18
|
# is use in Dir.glob.
|
20
|
-
|
19
|
+
DEFAULT_DEMO_LOCATION = '{demo,demos}'
|
21
20
|
|
22
21
|
# Initialize and execute.
|
23
22
|
def self.execute
|
@@ -156,12 +155,11 @@ module QED
|
|
156
155
|
end
|
157
156
|
|
158
157
|
#
|
159
|
-
|
160
158
|
def demos
|
161
159
|
files = self.files
|
162
|
-
types = Tilt.mappings.keys
|
160
|
+
types = %w{qed rdoc md markdown} #Tilt.mappings.keys
|
163
161
|
if files.empty?
|
164
|
-
files <<
|
162
|
+
files << DEFAULT_DEMO_LOCATION
|
165
163
|
end
|
166
164
|
files = files.map do |pattern|
|
167
165
|
Dir[pattern]
|
data/lib/qed/evaluator.rb
CHANGED
@@ -1,27 +1,15 @@
|
|
1
1
|
module QED
|
2
2
|
|
3
|
-
require 'tilt'
|
4
|
-
require 'nokogiri'
|
5
3
|
require 'qed/scope'
|
6
4
|
|
7
|
-
# =
|
8
|
-
#
|
9
|
-
#--
|
10
|
-
# TODO: Currently the Evaluator class uses #traverse to work
|
11
|
-
# thru the HTML document and trigger events accordingly. This
|
12
|
-
# works well enough for simple HTML documents --the kind produced
|
13
|
-
# by typical wiki-markup formats. However, for complex HTML it
|
14
|
-
# it will not produce ideal output (although the code segements
|
15
|
-
# should still run just fine). To counter this weakness, we will
|
16
|
-
# have to swtich to a more complex SAX parser in the future.
|
17
|
-
#--
|
5
|
+
# = Demonstrandum Evaluator
|
18
6
|
class Evaluator
|
19
7
|
|
20
8
|
#
|
21
9
|
def initialize(script, *observers)
|
22
10
|
@script = script
|
23
11
|
@file = script.file
|
24
|
-
@
|
12
|
+
@ast = script.parse
|
25
13
|
@scope = script.scope
|
26
14
|
@binding = script.binding
|
27
15
|
@advice = script.advice
|
@@ -31,75 +19,78 @@ module QED
|
|
31
19
|
|
32
20
|
#
|
33
21
|
def run
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
call_tag(element)
|
38
|
-
end
|
39
|
-
advise!(:after_document, @script)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
#
|
44
|
-
def call_tag(element)
|
45
|
-
advise!(:tag, element)
|
46
|
-
__send__("tag_#{element.name}", element)
|
22
|
+
advise!(:before_document, @script)
|
23
|
+
process
|
24
|
+
advise!(:after_document, @script)
|
47
25
|
end
|
48
26
|
|
49
|
-
# T A G S
|
50
|
-
|
51
27
|
#
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
when
|
58
|
-
|
59
|
-
else
|
60
|
-
Script.new(file, scope).run
|
28
|
+
def process
|
29
|
+
@ast.each do |section|
|
30
|
+
case section.type
|
31
|
+
when :code
|
32
|
+
evaluate_code(section)
|
33
|
+
when :text
|
34
|
+
evaluate_text(section)
|
61
35
|
end
|
62
36
|
end
|
63
37
|
end
|
64
38
|
|
65
39
|
#
|
66
|
-
def
|
67
|
-
advise!(:before_code,
|
40
|
+
def evaluate_code(section)
|
41
|
+
advise!(:before_code, section, @file)
|
68
42
|
begin
|
69
|
-
|
70
|
-
|
43
|
+
advise!(:code, section)
|
44
|
+
eval(section.text, @binding, @file, section.line)
|
45
|
+
#@scope.module_eval(section.text, @file, section.line)
|
46
|
+
pass!(section)
|
71
47
|
rescue Assertion => exception
|
72
|
-
fail!(
|
48
|
+
fail!(section, exception)
|
73
49
|
rescue Exception => exception
|
74
|
-
error!(
|
50
|
+
error!(section, exception)
|
75
51
|
end
|
76
|
-
advise!(:after_code,
|
52
|
+
advise!(:after_code, section, @file)
|
77
53
|
end
|
78
54
|
|
79
55
|
#
|
80
|
-
def
|
81
|
-
advise!(:
|
56
|
+
def evaluate_text(section)
|
57
|
+
advise!(:text, section)
|
58
|
+
evaluate_links(section)
|
59
|
+
advise!(:when, section)
|
82
60
|
end
|
83
61
|
|
84
62
|
#
|
85
|
-
def
|
86
|
-
|
63
|
+
def evaluate_links(section)
|
64
|
+
section.text.scan(/\[qed:\/\/(.*?)\]/) do |match|
|
65
|
+
file = $1
|
66
|
+
# relative to demo script
|
67
|
+
if File.exist?(File.join(@script.directory,file))
|
68
|
+
file = File.join(@script.directory,file)
|
69
|
+
end
|
70
|
+
# ruby or another demo
|
71
|
+
case File.extname(file)
|
72
|
+
when '.rb'
|
73
|
+
import!(file)
|
74
|
+
else
|
75
|
+
Script.new(@script.applique, file, @script.scope).run
|
76
|
+
end
|
77
|
+
end
|
87
78
|
end
|
88
79
|
|
89
80
|
#
|
90
|
-
def pass!(
|
91
|
-
advise!(:pass,
|
81
|
+
def pass!(section)
|
82
|
+
advise!(:pass, section)
|
92
83
|
end
|
93
84
|
|
94
85
|
#
|
95
|
-
def fail!(
|
96
|
-
advise!(:fail,
|
86
|
+
def fail!(section, exception)
|
87
|
+
advise!(:fail, section, exception)
|
97
88
|
#raise exception
|
98
89
|
end
|
99
90
|
|
100
91
|
#
|
101
|
-
def error!(
|
102
|
-
advise!(:error,
|
92
|
+
def error!(section, exception)
|
93
|
+
advise!(:error, section, exception)
|
103
94
|
#raise exception
|
104
95
|
end
|
105
96
|
|
@@ -114,7 +105,7 @@ module QED
|
|
114
105
|
def advise!(signal, *args)
|
115
106
|
@observers.each{ |o| o.update(signal, *args) }
|
116
107
|
#@scope.__advice__.call(signal, *args)
|
117
|
-
@advice.call(signal, *args)
|
108
|
+
@advice.call(@scope, signal, *args)
|
118
109
|
end
|
119
110
|
|
120
111
|
#
|
@@ -122,6 +113,11 @@ module QED
|
|
122
113
|
# @advice.call_when(match)
|
123
114
|
#end
|
124
115
|
|
116
|
+
##
|
117
|
+
#def method_missing(s, *a)
|
118
|
+
# super(s, *a) unless /^tag/ =~ s.to_s
|
119
|
+
#end
|
120
|
+
|
125
121
|
end
|
126
122
|
|
127
123
|
end
|
data/lib/qed/package.yml
ADDED
data/lib/qed/parser.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
module QED
|
2
|
+
|
3
|
+
# The parser breaks down a demonstandum into
|
4
|
+
# structured object to passed thru the script
|
5
|
+
# evaluator.
|
6
|
+
#
|
7
|
+
# Technically is defines it's own markup language
|
8
|
+
# but for interoperability sake it ...
|
9
|
+
class Parser
|
10
|
+
|
11
|
+
#
|
12
|
+
def initialize(file)
|
13
|
+
@lines = File.readlines(file).to_a
|
14
|
+
@ast = []
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
attr :ast
|
19
|
+
|
20
|
+
#
|
21
|
+
def parse
|
22
|
+
state = :text
|
23
|
+
linein = 0
|
24
|
+
|
25
|
+
text = ''
|
26
|
+
|
27
|
+
@lines.each_with_index do |line, lineno|
|
28
|
+
if /^\S/ =~ line
|
29
|
+
if state == :code
|
30
|
+
add_section(:code, text, linein)
|
31
|
+
linein = lineno
|
32
|
+
text = ''
|
33
|
+
end
|
34
|
+
state = :text
|
35
|
+
text << line
|
36
|
+
else
|
37
|
+
if state == :text
|
38
|
+
next if text.strip.empty?
|
39
|
+
add_section(:text, text, linein)
|
40
|
+
linein = lineno
|
41
|
+
text = ''
|
42
|
+
end
|
43
|
+
state = :code
|
44
|
+
text << line
|
45
|
+
end
|
46
|
+
end
|
47
|
+
add_section(state, text, linein)
|
48
|
+
@ast.reject!{ |sect| sect.type == :code && sect.text.strip.empty? }
|
49
|
+
return @ast
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
def add_section(state, text, lineno)
|
54
|
+
case state
|
55
|
+
when :code
|
56
|
+
if ast.last.raw?
|
57
|
+
@ast.last << text #clean_quote(text)
|
58
|
+
else
|
59
|
+
@ast << CodeSection.new(text, lineno)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
@ast << TextSection.new(text, lineno)
|
63
|
+
#cont = (/\.\.\.\s*^/ =~ text ? true : false)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: We need to preserve the indentation for the verbatim reporter.
|
68
|
+
#def clean_quote(text)
|
69
|
+
# text = text.tabto(0).chomp.sub(/\A\n/,'')
|
70
|
+
# if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
|
71
|
+
# text = md[1]
|
72
|
+
# end
|
73
|
+
# text.rstrip
|
74
|
+
#end
|
75
|
+
|
76
|
+
#
|
77
|
+
class Section
|
78
|
+
attr :text
|
79
|
+
attr :line
|
80
|
+
def initialize(text, line)
|
81
|
+
@text = text
|
82
|
+
@line = line
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
class TextSection < Section
|
88
|
+
attr :args
|
89
|
+
attr :cont
|
90
|
+
def initialize(text, line, *args)
|
91
|
+
@text = text
|
92
|
+
@line = line
|
93
|
+
@args = args
|
94
|
+
@cont = []
|
95
|
+
end
|
96
|
+
def <<(text)
|
97
|
+
@cont << clean_continuation(text)
|
98
|
+
@args << block_continuation(text)
|
99
|
+
end
|
100
|
+
def type
|
101
|
+
:text
|
102
|
+
end
|
103
|
+
# TODO: Use ':' or '...' ?
|
104
|
+
def raw?
|
105
|
+
#/\:\s*\Z/m =~ text
|
106
|
+
/\.\.\.\s*\Z/m =~ text
|
107
|
+
end
|
108
|
+
|
109
|
+
# Clean up the text, removing unccesseary white lines and triple
|
110
|
+
# quote brackets, but keep indention intact.
|
111
|
+
def clean_continuation(text)
|
112
|
+
text = text.chomp.sub(/\A\n/,'')
|
113
|
+
if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
|
114
|
+
text = md[1]
|
115
|
+
end
|
116
|
+
text.rstrip
|
117
|
+
end
|
118
|
+
|
119
|
+
# Block the text, removing white lines, triple quote brackets
|
120
|
+
# and indention.
|
121
|
+
def block_continuation(text)
|
122
|
+
text = text.tabto(0).chomp.sub(/\A\n/,'')
|
123
|
+
if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
|
124
|
+
text = md[1]
|
125
|
+
end
|
126
|
+
text.rstrip
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
class CodeSection < Section
|
132
|
+
#attr :args
|
133
|
+
def intialize(text, line) #, *args)
|
134
|
+
@text = text
|
135
|
+
@line = line
|
136
|
+
#@args = args
|
137
|
+
end
|
138
|
+
#def <<(arg)
|
139
|
+
# @args << arg
|
140
|
+
#end
|
141
|
+
def type
|
142
|
+
:code
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|