qed 2.6.3 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. data/.ruby +4 -3
  2. data/.yardopts +3 -0
  3. data/HISTORY.rdoc +71 -35
  4. data/README.rdoc +9 -10
  5. data/bin/qed +1 -1
  6. data/bin/qedoc +2 -1
  7. data/lib/qed.rb +2 -5
  8. data/lib/qed.yml +4 -3
  9. data/lib/qed/applique.rb +57 -24
  10. data/lib/qed/cli.rb +8 -0
  11. data/lib/qed/cli/qed.rb +124 -0
  12. data/lib/qed/demo.rb +35 -39
  13. data/lib/qed/document.rb +5 -3
  14. data/lib/qed/document/template.rhtml +1 -0
  15. data/lib/qed/evaluator.rb +227 -199
  16. data/lib/qed/parser.rb +60 -282
  17. data/lib/qed/reporter/abstract.rb +54 -58
  18. data/lib/qed/reporter/dotprogress.rb +6 -4
  19. data/lib/qed/reporter/html.rb +112 -31
  20. data/lib/qed/reporter/tapy.rb +95 -125
  21. data/lib/qed/reporter/verbatim.rb +80 -38
  22. data/lib/qed/scope.rb +35 -48
  23. data/lib/qed/session.rb +35 -140
  24. data/lib/qed/settings.rb +104 -67
  25. data/lib/qed/step.rb +237 -0
  26. data/{spec → qed}/01_demos.rdoc +0 -0
  27. data/{spec → qed}/02_advice.rdoc +18 -7
  28. data/qed/03_helpers.rdoc +44 -0
  29. data/{spec → qed}/04_samples.rdoc +4 -4
  30. data/{spec → qed}/05_quote.rdoc +3 -3
  31. data/{spec → qed}/07_toplevel.rdoc +0 -0
  32. data/{spec → qed}/08_cross_script.rdoc +0 -0
  33. data/{spec → qed}/09_cross_script.rdoc +0 -0
  34. data/{spec → qed}/10_constant_lookup.rdoc +2 -2
  35. data/qed/11_embedded_rules.rdoc +46 -0
  36. data/{test/integration/topcode.rdoc → qed/99_issues/02_topcode.rdoc} +0 -0
  37. data/{spec → qed}/applique/constant.rb +0 -0
  38. data/{spec → qed}/applique/env.rb +0 -0
  39. data/{spec → qed}/applique/fileutils.rb +0 -0
  40. data/{spec → qed}/applique/markup.rb +0 -0
  41. data/{spec → qed}/applique/toplevel.rb +0 -0
  42. data/{spec → qed}/helpers/advice.rb +6 -7
  43. data/{spec → qed}/helpers/toplevel.rb +0 -0
  44. data/{spec → qed}/samples/data.txt +0 -0
  45. data/{spec → qed}/samples/table.yml +0 -0
  46. metadata +44 -39
  47. data/LICENSE.rdoc +0 -31
  48. data/SPECSHEET.rdoc +0 -456
  49. data/lib/qed/advice.rb +0 -158
  50. data/lib/qed/reporter/bullet.rb +0 -91
  51. data/lib/qed/reporter/dtrace.rb +0 -67
  52. data/lib/yard-qed.rb +0 -1
  53. data/spec/03_helpers.rdoc +0 -43
  54. data/spec/applique/quote.rb +0 -4
  55. data/spec/helpers/sample.rb +0 -4
@@ -0,0 +1,8 @@
1
+ if RUBY_VERSION < '1.9'
2
+ require 'qed/cli/qed'
3
+ require 'qed/cli/qedoc'
4
+ else
5
+ require_relative('cli/qed')
6
+ require_relative('cli/qedoc')
7
+ end
8
+
@@ -0,0 +1,124 @@
1
+ module QED
2
+
3
+ require 'qed/session'
4
+
5
+ #
6
+ def self.cli(*argv)
7
+ Session.cli(*argv)
8
+ end
9
+
10
+ class Session
11
+
12
+ #
13
+ def self.cli(*argv)
14
+ require 'optparse'
15
+ require 'shellwords'
16
+
17
+ files, options = cli_parse(argv)
18
+
19
+ #if files.empty?
20
+ # puts "No files."
21
+ # exit -1
22
+ #end
23
+
24
+ session = Session.new(files, options)
25
+ success = session.run
26
+
27
+ exit -1 unless success
28
+ end
29
+
30
+ # Instance of OptionParser
31
+ def self.cli_parse(argv)
32
+ options = {}
33
+ options_parser = OptionParser.new do |opt|
34
+ opt.banner = "Usage: qed [options] <files...>"
35
+
36
+ opt.separator("Custom Profiles:") unless settings.profiles.empty?
37
+
38
+ settings.profiles.each do |name, value|
39
+ o = "--#{name}"
40
+ opt.on(o, "#{name} custom profile") do
41
+ options[:profile] = name.to_sym
42
+ end
43
+ end
44
+
45
+ opt.separator("Report Formats (pick one):")
46
+ #opt.on('--dotprogress', '-d', "use dot-progress reporter [default]") do
47
+ # options[:format] = :dotprogress
48
+ #end
49
+ opt.on('--verbatim', '-v', "shortcut for verbatim reporter") do
50
+ options[:format] = :verbatim
51
+ end
52
+ opt.on('--tapy', '-y', "shortcut for TAP-Y reporter") do
53
+ options[:format] = :tapy
54
+ end
55
+ #opt.on('--bullet', '-b', "use bullet-point reporter") do
56
+ # options[:format] = :bullet
57
+ #end
58
+ #opt.on('--html', '-h', "use underlying HTML reporter") do
59
+ # options[:format] = :html
60
+ #end
61
+ #opt.on('--script', "psuedo-reporter") do
62
+ # options[:format] = :script # psuedo-reporter
63
+ #end
64
+ opt.on('--format', '-f FORMAT', "use custom reporter") do |format|
65
+ options[:format] = format.to_sym
66
+ end
67
+
68
+ opt.separator("Control Options:")
69
+ opt.on('--comment', '-c', "run comment code") do
70
+ options[:mode] = :comment
71
+ end
72
+ opt.on('--profile', '-p NAME', "load runtime profile") do |name|
73
+ options[:profile] = name
74
+ end
75
+ opt.on('--loadpath', "-I PATH", "add paths to $LOAD_PATH") do |paths|
76
+ options[:loadpath] = paths.split(/[:;]/).map{|d| File.expand_path(d)}
77
+ end
78
+ opt.on('--require', "-r LIB", "require library") do |paths|
79
+ options[:requires] = paths.split(/[:;]/)
80
+ end
81
+ opt.on('--rooted', '-R', "run from project root instead of temporary directory") do
82
+ options[:rooted] = true
83
+ end
84
+ # COMMIT:
85
+ # The qed command --trace option takes a count.
86
+ # Use 0 to mean all.
87
+ opt.on('--trace', '-t [COUNT]', "show full backtraces for exceptions") do |cnt|
88
+ #options[:trace] = true
89
+ ENV['trace'] = cnt
90
+ end
91
+ opt.on('--warn', "run with warnings turned on") do
92
+ $VERBOSE = true # wish this were called $WARN!
93
+ end
94
+ opt.on('--debug', "exit immediately upon raised exception") do
95
+ $DEBUG = true
96
+ end
97
+
98
+ opt.separator("Optional Commands:")
99
+ opt.on_tail('--version', "display version") do
100
+ puts "QED #{QED::VERSION}"
101
+ exit
102
+ end
103
+ opt.on_tail('--copyright', "display copyrights") do
104
+ puts "Copyright (c) 2008 Thomas Sawyer, Apache 2.0 License"
105
+ exit
106
+ end
107
+ opt.on_tail('--help', '-h', "display this help message") do
108
+ puts opt
109
+ exit
110
+ end
111
+ end
112
+ options_parser.parse!(argv)
113
+ return argv, options
114
+ end
115
+
116
+ # TODO: Pass to Session class, instead of acting global.
117
+ # It is used at the class level to get profiles for the cli.
118
+ def self.settings
119
+ @settings ||= Settings.new
120
+ end
121
+
122
+ end
123
+
124
+ end
@@ -7,7 +7,7 @@ module QED
7
7
  require 'qed/evaluator'
8
8
  require 'qed/applique'
9
9
 
10
- # The Demo class ecapsulates a demonstration document.
10
+ # The Demo class ecapsulates a demonstrandum script.
11
11
  #
12
12
  class Demo
13
13
 
@@ -17,27 +17,30 @@ module QED
17
17
  # Parser mode.
18
18
  attr :mode
19
19
 
20
- # Working directory.
21
- attr :cwd
22
-
23
- # Scope to run demonstration within. (Known as a "World" in Cucumber.)
24
- attr :scope
25
-
26
- # New Script
20
+ # Steup new Demo instance.
21
+ #
22
+ # @param [String] file
23
+ # Path to demo file.
24
+ #
25
+ # @param [Hash] options
26
+ #
27
+ # @option options [Symbol] :mode
28
+ # Either `:comment` or other for normal mode.
29
+ #
30
+ # @option options [Strng] :at
31
+ # Working directory.
32
+ #
33
+ # @option options [Array] :applique
34
+ # Overriding applique. Used to import demos into other demos safely.
35
+ #
36
+ # @option options [Scope] :scope
37
+ # Overriding scope, otherwise new Scope instance is created.
38
+ #
27
39
  def initialize(file, options={})
28
- @file = file
29
- @mode = options[:mode]
30
- @cwd = options[:at] || fallback_cwd
40
+ @file = file
31
41
 
32
- @scope = options[:scope] || Scope.new(applique, cwd, file)
33
-
34
- @binding = @scope.__binding__
35
- #apply_environment
36
- end
37
-
38
- # One binding per demo.
39
- def binding
40
- @binding #||= @scope.__binding__
42
+ @mode = options[:mode]
43
+ @applique = options[:applique]
41
44
  end
42
45
 
43
46
  # Expanded dirname of +file+.
@@ -50,25 +53,24 @@ module QED
50
53
  @name ||= File.basename(file).chomp(File.extname(file))
51
54
  end
52
55
 
53
- #
54
- def evaluate(code, line)
55
- #eval(code, @binding, @file, line)
56
- @scope.eval(code, @file, line)
57
- end
58
-
59
56
  # Returns a cached Array of Applique modules.
60
57
  def applique
61
58
  @applique ||= (
62
59
  list = [Applique.new]
63
60
  applique_locations.each do |location|
64
- Dir[location + '/**/*.rb'].each do |file|
65
- list << Applique.new(file)
61
+ Dir[location + '/**/*'].each do |file|
62
+ list << Applique.for(file)
66
63
  end
67
64
  end
68
65
  list
69
66
  )
70
67
  end
71
68
 
69
+ #
70
+ def applique_prime
71
+ applique.first
72
+ end
73
+
72
74
  # Returns a list of applique directories to be used by this
73
75
  # demonstrastion.
74
76
  def applique_locations
@@ -94,20 +96,19 @@ module QED
94
96
 
95
97
  # Parse and cache demonstration script.
96
98
  #
97
- # @return [Array] abstract syntax tree
99
+ # @return [Array] list of steps (abstract syntax tree)
98
100
  alias_method :parse, :steps
99
101
 
100
102
  # Get a new Parser instance for this demo.
101
103
  #
102
104
  # @return [Parser] parser for this demo
103
105
  def parser
104
- Parser.new(file, :mode=>mode)
106
+ Parser.new(self, :mode=>mode)
105
107
  end
106
108
 
107
- #
108
- def run(*observers)
109
- evaluator = Evaluator.new(self, *observers)
110
- evaluator.run
109
+ # Run demo through {Evaluator} instance with given observers.
110
+ def run(options={})
111
+ Evaluator.run(self, options)
111
112
  end
112
113
 
113
114
  #
@@ -123,11 +124,6 @@ module QED
123
124
  # )
124
125
  #end
125
126
 
126
- # This shouldn't be needed, but is here as a precaution.
127
- def fallback_cwd
128
- @dir ||= File.join(Dir.tmpdir, 'qed', File.filename(Dir.pwd), Time.new.strftime("%Y%m%d%H%M%S"))
129
- end
130
-
131
127
  end
132
128
 
133
129
  end
@@ -78,7 +78,11 @@ module QED
78
78
  files = files.reject{ |f| File.directory?(f) }
79
79
  files = files.reject{ |f| File.extname(f) == '.rb' }
80
80
  files = files.reject{ |f| /(fixtures|helpers)\// =~ f }
81
- files.sort
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
82
86
  )
83
87
  end
84
88
 
@@ -103,8 +107,6 @@ module QED
103
107
  #end
104
108
  #files.sort!
105
109
 
106
- #TODO: load .config/qedrc.rb
107
-
108
110
  if dryrun or $DEBUG
109
111
  puts demo_files.sort.join(" ")
110
112
  end
@@ -1,6 +1,7 @@
1
1
  <html>
2
2
  <head>
3
3
  <title><%= title %></title>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
4
5
 
5
6
  <!-- TODO: most of the css before the '*' can be removed. -->
6
7
 
@@ -2,230 +2,203 @@ module QED
2
2
 
3
3
  require 'qed/scope'
4
4
 
5
- # = Demonstrandum Evaluator
5
+ # Demonstrandum Evaluator is responsible for running demo scripts.
6
+ #
6
7
  class Evaluator
7
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
+ # @param [Array] observers
20
+ # Objects that respond to observable interface.
21
+ # Typically this is just a Reporter instance.
8
22
  #
9
- def initialize(script, *observers)
10
- @script = script
11
- @steps = script.steps
23
+ def initialize(demo, options={})
24
+ @demo = demo
25
+ @steps = demo.steps
12
26
 
13
- #@file = script.file
14
- #@scope = script.scope
15
- #@binding = script.binding
16
- #@advice = script.advice
27
+ #@settings = options[:settings]
28
+ @applique = options[:applique] # BOOLEAN FLAG
17
29
 
18
- @observers = observers
19
- end
30
+ @observers = options[:observers].to_a
31
+ @observers += applique_observers
20
32
 
21
- #
22
- def run
23
- advise!(:before_demo, @script)
24
- advise!(:demo, @script)
25
- run_steps
26
- advise!(:after_demo, @script)
33
+ @scope = options[:scope] || Scope.new(demo)
27
34
  end
28
35
 
36
+ # Collect applique all the signal-based advice and wrap their evaluation
37
+ # in observable procedure calls.
29
38
  #
30
- def run_steps #process
31
- @steps.each do |step|
32
- evaluate(step)
39
+ def applique_observers
40
+ demo = @demo
41
+ demo.applique.map do |a|
42
+ Proc.new do |type, *args|
43
+ proc = a.__signals__[type.to_sym]
44
+ @scope.instance_exec(*args, &proc) if proc
45
+ end
33
46
  end
34
47
  end
35
48
 
49
+ public
50
+
51
+ # The Demo being evaluated.
36
52
  #
37
- def evaluate(step)
38
- type = step.type
39
- advise!(:before_step, step) #, @script.file)
40
- advise!("before_#{type}".to_sym, step) #, @script.file)
41
- case type
42
- when :head
43
- evaluate_head(step)
44
- when :desc
45
- evaluate_desc(step)
46
- when :data
47
- evaluate_data(step)
48
- when :code
49
- evaluate_code(step)
50
- else
51
- raise "fatal: unknown #{type}"
52
- end
53
- advise!("after_#{type}".to_sym, step) #, @script.file)
54
- advise!(:after_step, step) #, @script.file)
55
- end
53
+ # @return [Demo]
54
+ attr :demo
56
55
 
56
+ # The observers.
57
57
  #
58
- def evaluate_head(step)
59
- advise!(:head, step)
60
- end
58
+ attr :observers
61
59
 
60
+ # Run the demo.
62
61
  #
63
- def evaluate_desc(step)
64
- evaluate_links(step)
62
+ def run
63
+ advise!(:before_demo, @demo)
65
64
  begin
66
- advise!(:desc, step)
67
- advise!(:when, step) # triggers matchers
68
- rescue SystemExit
69
- pass!(step)
70
- #rescue Assertion => exception
71
- # fail!(step, exception)
72
- rescue Exception => exception
73
- if exception.assertion?
74
- fail!(step, exception)
75
- else
76
- error!(step, exception)
77
- end
78
- else
79
- pass!(step)
65
+ advise!(:demo, @demo)
66
+ run_steps
67
+ ensure
68
+ advise!(:after_demo, @demo)
80
69
  end
81
70
  end
82
71
 
72
+ private
73
+
74
+ # Interate over each step and evaluate it.
83
75
  #
84
- def evaluate_data(step)
85
- #advise!(:data, step)
86
- begin
87
- advise!(:data, step)
88
- rescue SystemExit
89
- pass!(step)
90
- #rescue Assertion => exception
91
- # fail!(step, exception)
92
- rescue Exception => exception
93
- if exception.assertion?
94
- fail!(step, exception)
95
- else
96
- error!(step, exception)
97
- end
98
- else
99
- pass!(step)
76
+ def run_steps
77
+ @steps.each do |step|
78
+ evaluate(step)
100
79
  end
101
80
  end
81
+
82
+ # Evaluate a step.
83
+ #
84
+ # @macro [new] step
85
+ #
86
+ # @param [Step] step
87
+ # The step being evaluated.
88
+ #
89
+ # @return nothing
90
+ def evaluate(step)
91
+ advise!(:before_step, step)
92
+ advise!(:step, step)
102
93
 
103
- # Evaluate a demo step.
104
- def evaluate_code(step)
105
- begin
106
- advise!(:code, step)
107
- @script.evaluate(step.code, step.lineno)
108
- rescue SystemExit
109
- pass!(step) # TODO: skip!(step)
110
- #rescue Assertion => exception
111
- # fail!(step, exception)
112
- rescue Exception => exception
113
- if exception.assertion?
114
- fail!(step, exception)
115
- else
116
- error!(step, exception)
117
- end
94
+ evaluate_links(step) unless step.heading?
95
+
96
+ if step.assertive? && !@applique
97
+ evaluate_test(step)
118
98
  else
119
- pass!(step)
99
+ evaluate_applique(step)
120
100
  end
101
+
102
+ advise!(:after_step, step)
121
103
  end
122
104
 
123
105
  # TODO: Not sure how to handle loading links in --comment runner mode.
124
- # TODO: Do not think Scope should be reuseud by imported demo.
106
+
107
+ # TODO: Should scope be reused by imported demo ?
108
+
109
+ # If there are embedded links in the step description than extract
110
+ # them and load them in.
111
+ #
112
+ # @macro step
125
113
  def evaluate_links(step)
126
114
  step.text.scan(/\[qed:\/\/(.*?)\]/) do |match|
127
115
  file = $1
128
- # relative to demo script
129
- if File.exist?(File.join(@script.directory,file))
130
- file = File.join(@script.directory,file)
116
+ # relative to demo demo
117
+ if File.exist?(File.join(@demo.directory,file))
118
+ file = File.join(@demo.directory,file)
131
119
  end
132
- # ruby or another demo
133
- case File.extname(file)
134
- when '.rb'
135
- import!(file)
136
- else
137
- Demo.new(file, :scope=>@script.scope).run
120
+
121
+ advise!(:before_import, file)
122
+ begin
123
+ advise!(:import, file)
124
+ case File.extname(file)
125
+ when '.rb'
126
+ Kernel.eval(File.read(file), @scope.__binding__, file)
127
+ else
128
+ demo = Demo.new(file)
129
+ Evaluator.new(demo, :scope=>@scope).run
130
+ end
131
+ ensure
132
+ advise!(:after_import, file)
138
133
  end
139
134
  end
140
135
  end
141
136
 
137
+ # Evaluate step at the *applique level*. This means the execution
138
+ # of code and even matcher evaluations will not be captured by a
139
+ # rescue clause.
142
140
  #
143
- def pass!(step)
144
- advise!(:pass, step)
145
- end
146
-
147
- #
148
- def fail!(step, exception)
149
- advise!(:fail, step, exception)
150
- #raise exception
141
+ # @macro step
142
+ def evaluate_applique(step)
143
+ advise!(:before_applique, step)
144
+ begin
145
+ advise!(:applique, step)
146
+ evaluate_matchers(step)
147
+ evaluate_example(step)
148
+ ensure
149
+ advise!(:after_applique, step)
150
+ end
151
151
  end
152
152
 
153
- #
154
- def error!(step, exception)
155
- advise!(:error, step, exception)
156
- #raise exception
157
- end
153
+ # Exceptions to always raise regardless.
154
+ FORCED_EXCEPTIONS = [NoMemoryError, SignalException, Interrupt] #, SystemExit]
158
155
 
156
+ # Evaluate the step's matchaters and code sample, wrapped in a begin-rescue
157
+ # clause.
159
158
  #
160
- def import!(file)
161
- advise!(:unload) # should this also occur just befor after_demo ?
162
- eval(File.read(file), @script.binding, file)
163
- advise!(:load, file)
164
- end
165
-
166
- # Dispatch event to observers and advice.
167
- def advise!(signal, *args)
168
- @observers.each{ |o| o.update(signal, *args) }
169
-
170
- #@script.advise(signal, *args)
171
- case signal
172
- when :when
173
- call_matchers(*args)
159
+ # @macro step
160
+ def evaluate_test(step)
161
+ advise!(:before_test, step)
162
+ begin
163
+ advise!(:test, step) # name ?
164
+ evaluate_matchers(step)
165
+ evaluate_example(step)
166
+ rescue *FORCED_EXCEPTIONS
167
+ raise
168
+ rescue SystemExit # TODO: why pass on SystemExit ?
169
+ advise!(:pass, step)
170
+ #rescue Assertion => exception
171
+ # advise!(:fail, step, exception)
172
+ rescue Exception => exception
173
+ if exception.assertion?
174
+ advise!(:fail, step, exception)
175
+ else
176
+ advise!(:error, step, exception)
177
+ end
174
178
  else
175
- call_signals(signal, *args)
179
+ advise!(:pass, step)
180
+ ensure
181
+ advise!(:after_test, step)
176
182
  end
177
183
  end
178
184
 
185
+ # Evaluate the step's example in the demo's context, if the example
186
+ # is source code.
179
187
  #
180
- #def advise_when!(match)
181
- # @advice.call_when(match)
182
- #end
183
-
184
- # React to an event.
185
- #
186
- # TODO: Should events short circuit on finding first match?
187
- # In other words, should there be only one of each type of signal
188
- # ragardless of how many applique layers?
189
- def call_signals(type, *args)
190
- @script.applique.each do |a|
191
- signals = a.__signals__
192
- proc = signals[type.to_sym]
193
- #signals.each do |set|
194
- #proc = set[type.to_sym]
195
- #proc.call(*args) if proc
196
- @script.scope.instance_exec(*args, &proc) if proc
197
- #end
198
- end
199
-
200
- #@script.applique.each do |a|
201
- # signals = a.__signals__
202
- # proc = signals[type.to_sym]
203
- # if proc
204
- # @script.scope.instance_exec(*args, &proc)
205
- # break
206
- # end
207
- #end
208
-
209
- #meth = "qed_#{type}"
210
- #if @script.scope.respond_to?(meth)
211
- # meth = @script.scope.method(meth)
212
- # if meth.arity == 0
213
- # meth.call
214
- # else
215
- # meth.call(*args)
216
- # end
217
- #end
218
-
219
- #@script.scope.__send__(meth, *args)
188
+ # @macro step
189
+ def evaluate_example(step)
190
+ @scope.evaluate(step.code, step.file, step.lineno) if step.code?
220
191
  end
221
192
 
193
+ # Search the step's description for applique matches and
194
+ # evaluate them.
222
195
  #
223
- def call_matchers(section)
224
- match = section.text
225
- args = section.arguments
226
- @script.applique.each do |a|
227
- matchers = a.__matchers__
228
- matchers.each do |(patterns, proc)|
196
+ # @macro step
197
+ def evaluate_matchers(step)
198
+ match = step.text
199
+
200
+ @demo.applique.each do |app|
201
+ app.__matchers__.each do |(patterns, proc)|
229
202
  compare = match
230
203
  matched = true
231
204
  params = []
@@ -237,6 +210,7 @@ module QED
237
210
  regex = match_string_to_regexp(pattern)
238
211
  end
239
212
  if md = regex.match(compare)
213
+ advise!(:match, step, md) # ADVISE !
240
214
  params.concat(md[1..-1])
241
215
  compare = md.post_match
242
216
  else
@@ -245,46 +219,100 @@ module QED
245
219
  end
246
220
  end
247
221
  if matched
248
- params += args
249
- #proc.call(*params)
250
- @script.scope.instance_exec(*params, &proc)
222
+ #args = [params, arguments].reject{|e| e == []} # use single argument for params in 3.0?
223
+ args = params
224
+ args = args + [step.sample_text] if step.data?
225
+ args = proc.arity < 0 ? args : args[0,proc.arity]
226
+
227
+ #@demo.scope
228
+ @scope.instance_exec(*args, &proc) #proc.call(*args)
251
229
  end
252
230
  end
253
231
  end
254
232
  end
255
233
 
234
+ #
235
+ SPLIT_PATTERNS = [ /(\(\(.*?\)\)(?!\)))/, /(\/\(.*?\)\/)/, /(\/\?.*?\/)/ ]
236
+
237
+ #
238
+ SPLIT_PATTERN = Regexp.new(SPLIT_PATTERNS.join('|'))
239
+
256
240
  # Convert matching string into a regular expression. If the string
257
241
  # contains double parenthesis, such as ((.*?)), then the text within
258
242
  # them is treated as in regular expression and kept verbatium.
259
243
  #
260
- # TODO: Better way to isolate regexp. Maybe ?:(.*?) or /(.*?)/.
261
- #
262
- # TODO: Now that we can use multi-patterns, do we still need this?
263
- #
264
244
  def match_string_to_regexp(str)
265
- str = str.split(/(\(\(.*?\)\))(?!\))/).map{ |x|
266
- x =~ /\A\(\((.*)\)\)\Z/ ? $1 : Regexp.escape(x)
267
- }.join
268
- str = str.gsub(/\\\s+/, '\s+')
245
+ re = nil
246
+ str = str.split(SPLIT_PATTERN).map do |x|
247
+ case x
248
+ when /\A\(\((.*?)\)\)(?!\))/
249
+ $1
250
+ when /\A\/(\(.*?\))\//
251
+ $1
252
+ when /\A\/(\?.*?)\//
253
+ "(#{$1})"
254
+ else
255
+ Regexp.escape(x)
256
+ end
257
+ end.join
258
+
259
+ str = str.gsub(/\\\s+/, '\s+') # Replace space with variable space.
260
+
269
261
  Regexp.new(str, Regexp::IGNORECASE)
262
+ end
263
+
264
+ =begin
265
+ # The following code works as well, and can provide a MatchData
266
+ # object instead of just matching params, but I call YAGNI on that
267
+ # and it has two benefits. 1) the above code is faster, and 2)
268
+ # using params allows |(name1, name2)| in rule blocks.
269
+
270
+ #
271
+ def evaluate_matchers(step)
272
+ match = step.text
273
+ args = step.arguments
274
+ @demo.applique.each do |a|
275
+ matchers = a.__matchers__
276
+ matchers.each do |(patterns, proc)|
277
+ re = build_matcher_regexp(*patterns)
278
+ if md = re.match(match)
279
+ #params = [step.text.strip] + params
280
+ #proc.call(*params)
281
+ @demo.scope.instance_exec(md, *args, &proc)
282
+ end
283
+ end
284
+ end
285
+ end
270
286
 
271
- #rexps = []
272
- #str = str.gsub(/\(\((.*?)\)\)/) do |m|
273
- # rexps << '(' + $1 + ')'
274
- # "\0"
275
- #end
276
- #str = Regexp.escape(str)
277
- #rexps.each do |r|
278
- # str = str.sub("\0", r)
279
- #end
280
- #str = str.gsub(/(\\\ )+/, '\s+')
281
- #Regexp.new(str, Regexp::IGNORECASE)
287
+ #
288
+ def build_matcher_regexp(*patterns)
289
+ parts = []
290
+ patterns.each do |pattern|
291
+ case pattern
292
+ when Regexp
293
+ parts << pattern
294
+ else
295
+ parts << match_string_to_regexp(pattern)
296
+ end
297
+ end
298
+ Regexp.new(parts.join('.*?'), Regexp::MULTILINE)
282
299
  end
300
+ =end
283
301
 
284
- ##
285
- #def method_missing(s, *a)
286
- # super(s, *a) unless /^tag/ =~ s.to_s
287
- #end
302
+ # TODO: pass demo to advice?
303
+
304
+ # Dispatch an advice event to observers.
305
+ #
306
+ # @param [Symbol] signal
307
+ # The name of the dispatch.
308
+ #
309
+ # @param [Array<Object>] args
310
+ # Any arguments to send along witht =the signal to the observers.
311
+ #
312
+ # @return nothing
313
+ def advise!(signal, *args)
314
+ @observers.each{ |o| o.call(signal.to_sym, *args) }
315
+ end
288
316
 
289
317
  end
290
318