qed 2.6.3 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
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