qed 1.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+ module QED
2
+ module Reporter #:nodoc:
3
+
4
+ require 'qed/reporter/base'
5
+
6
+ # = Html Reporter
7
+ #
8
+ class Html < BaseClass
9
+
10
+ #
11
+ def pass(step)
12
+ step['class'] = 'pass' # TODO add class not replace
13
+ step['style'] = 'color: green;' # TODO add style not replace
14
+ end
15
+
16
+ #
17
+ def fail(step, assertion)
18
+ step['class'] = 'fail' # TODO add class not replace
19
+ step['style'] = 'color: red;' # TODO add style not replace
20
+
21
+ msg = "\n"
22
+ msg << " ##### FAIL #####\n"
23
+ msg << " # " + assertion.to_s
24
+ msg << "\n"
25
+
26
+ step.add_child(Nokogiri::HTML.fragment(msg))
27
+ end
28
+
29
+ #
30
+ def error(step, exception)
31
+ raise exception if $DEBUG
32
+
33
+ step['class'] = 'error' # TODO add class not replace
34
+ step['style'] = 'color: red;' # TODO add style not replace
35
+
36
+ msg = "\n"
37
+ msg << " ##### ERROR #####\n"
38
+ msg << " # " + exception.to_s + "\n"
39
+ msg << " # " + exception.backtrace[0]
40
+ msg << "\n"
41
+
42
+ step.add_child(Nokogiri::HTML.fragment(msg))
43
+ end
44
+
45
+ #
46
+ def after_document(demo)
47
+ io.puts demo.document.to_s
48
+ end
49
+
50
+ #def report(str)
51
+ # count[-1] += 1 unless count.empty?
52
+ # str = str.chomp('.') + '.'
53
+ # str = count.join('.') + ' ' + str
54
+ # io.puts str.strip
55
+ #end
56
+
57
+ end
58
+
59
+ end#module Reporter
60
+ end#module QED
61
+
@@ -3,74 +3,60 @@ module Reporter #:nodoc:
3
3
 
4
4
  require 'qed/reporter/base'
5
5
 
6
- # = Verbatim Reporter
6
+ # = Verbose ANSI Console Reporter
7
7
  #
8
8
  class Verbatim < BaseClass
9
9
 
10
- #def report_step(step)
11
- # super
12
- # if step.code
13
- # #str = "(%s) %s" % [count.join('.'), str.tab(6).strip]
14
- # #io.puts "* #{step.to_s.tab(2).strip}"
15
- # #io.puts
16
- # #io.puts step.to_s
17
- # #io.puts
18
- # else
19
- # #io.puts "#{step}\n" # TODO: This never happens.
20
- # end
21
- #end
22
-
23
- #def report_intro
24
- # io.puts
25
- #end
26
-
27
- def report_header(step)
28
- io.print ANSICode.bold("#{step}")
29
- end
30
-
31
- def report_comment(step)
32
- io.print step
33
- end
34
-
35
10
  #
36
- def report_macro(step)
37
- #io.puts
38
- #io.puts step.text
39
- io.print ANSICode.magenta("#{step}")
40
- #io.puts
11
+ def tag(element)
12
+ case element.name
13
+ when 'pre'
14
+ # none
15
+ when /h\d/
16
+ io.print ANSI::Code.bold("#{element.text.strip}\n\n")
17
+ when 'p'
18
+ io.print "#{element.text.strip}\n\n"
19
+ #when 'a'
20
+ # io.print element.to_s
21
+ when 'ul', 'ol'
22
+ io.print "\n"
23
+ when 'li'
24
+ io.print "* #{element.text.strip}\n"
25
+ end
41
26
  end
42
27
 
43
28
  #
44
- def report_pass(step)
45
- io.print ANSICode.green("#{step}")
29
+ def pass(step)
30
+ txt = step.text.rstrip.sub("\n",'')
31
+ io.print ANSI::Code.green("#{txt}\n\n")
46
32
  end
47
33
 
48
- def report_fail(step, error)
49
- tab = step.to_s.index(/\S/) #step.tab
50
- io.print ANSICode.red("#{step}")
34
+ #
35
+ def fail(step, error)
36
+ txt = step.text.rstrip.sub("\n",'')
37
+ tab = step.text.index(/\S/) - 1
38
+ io.print ANSI::Code.red("#{txt}\n\n")
51
39
  msg = []
52
- msg << ANSICode.bold(ANSICode.red("FAIL: ")) + error.to_str
53
- msg << ANSICode.bold(error.backtrace[0].chomp(":in \`_binding'"))
40
+ msg << ANSI::Code.bold(ANSI::Code.red("FAIL: ")) + error.to_str
41
+ msg << ANSI::Code.bold(clean_backtrace(error.backtrace[0]))
54
42
  io.puts msg.join("\n").tabto(tab||2)
55
43
  io.puts
56
44
  end
57
45
 
58
- def report_error(step, error)
46
+ #
47
+ def error(step, error)
59
48
  raise error if $DEBUG
60
- tab = step.to_s.index(/\S/) #step.tab
61
- io.print ANSICode.red("#{step}")
49
+ txt = step.text.rstrip.sub("\n",'')
50
+ tab = step.text.index(/\S/) - 1
51
+ io.print ANSI::Code.red("#{txt}\n\n")
62
52
  msg = []
63
- msg << ANSICode.bold(ANSICode.red("ERROR: ")) + error.to_str.sub(/for QED::Context.*?$/,'')
64
- msg << ANSICode.bold(error.backtrace[0].chomp(":in \`_binding'"))
53
+ msg << ANSI::Code.bold(ANSI::Code.red("ERROR: ")) + error.to_str.sub(/for QED::Context.*?$/,'')
54
+ msg << ANSI::Code.bold(clean_backtrace(error.backtrace[0]))
65
55
  #msg = ANSICode.red(msg)
66
56
  io.puts msg.join("\n").tabto(tab||2)
67
57
  io.puts
68
58
  end
69
59
 
70
- def report_step_end(step)
71
- io.puts
72
- end
73
-
74
60
  #def report(str)
75
61
  # count[-1] += 1 unless count.empty?
76
62
  # str = str.chomp('.') + '.'
@@ -82,6 +68,14 @@ module Reporter #:nodoc:
82
68
  # puts ANSICode.magenta(set.to_yaml.tabto(2))
83
69
  #end
84
70
 
71
+ #
72
+ #def macro(step)
73
+ # #io.puts
74
+ # #io.puts step.text
75
+ # io.print ANSICode.magenta("#{step}")
76
+ # #io.puts
77
+ #end
78
+
85
79
  end
86
80
 
87
81
  end #module Reporter
data/lib/qed/scope.rb ADDED
@@ -0,0 +1,77 @@
1
+ module QED
2
+
3
+ require 'ae'
4
+ require 'qed/advice'
5
+
6
+ #--
7
+ # TODO: Replace Scope for TOPLEVEL?
8
+ #++
9
+ class Scope
10
+
11
+ include Advisable
12
+
13
+ def __binding__
14
+ @__binding__ ||= binding
15
+ end
16
+
17
+ # Table-based steps.
18
+ #--
19
+ # TODO: Utilize HTML table element for tables.
20
+ #++
21
+ def Table(file=nil, &blk)
22
+ file = file || @_tables.last
23
+ tbl = YAML.load(File.new(file))
24
+ tbl.each do |set|
25
+ blk.call(*set)
26
+ end
27
+ @__tables__ ||= []
28
+ @__tables__ << file
29
+ end
30
+
31
+ # Read/Write a static data fixture.
32
+ #--
33
+ # TODO: Perhaps #Data would be best as some sort of Kernel extension.
34
+ #++
35
+ def Data(file, &content)
36
+ raise if File.directory?(file)
37
+ if content
38
+ FileUtils.mkdir_p(File.dirname(fname))
39
+ case File.extname(file)
40
+ when '.yml', '.yaml'
41
+ File.open(file, 'w'){ |f| f << content.call.to_yaml }
42
+ else
43
+ File.open(file, 'w'){ |f| f << content.call }
44
+ end
45
+ else
46
+ #raise LoadError, "no such fixture file -- #{fname}" unless File.exist?(fname)
47
+ case File.extname(file)
48
+ when '.yml', '.yaml'
49
+ YAML.load(File.new(file))
50
+ else
51
+ File.read(file)
52
+ end
53
+ end
54
+ end
55
+
56
+ # Code match-and-transform procedure.
57
+ #
58
+ # This is useful to transform human readable code examples
59
+ # into proper exectuable code. For example, say you want to
60
+ # run shell code, but want to make if look like typical
61
+ # shelle examples:
62
+ #
63
+ # $ cp fixture/a.rb fixture/b.rb
64
+ #
65
+ # You can use a transform to convert lines starting with '$'
66
+ # into executable Ruby using #system.
67
+ #
68
+ # system('cp fixture/a.rb fixture/b.rb')
69
+ #
70
+ #def Transform(pattern=nil, &procedure)
71
+ #
72
+ #end
73
+
74
+ end
75
+
76
+ end
77
+
data/lib/qed/script.rb CHANGED
@@ -1,449 +1,116 @@
1
1
  module QED
2
2
  require 'yaml'
3
- require 'facets/dir/ascend'
3
+ require 'tilt'
4
+ require 'nokogiri'
4
5
 
5
- require 'ae'
6
+ require 'facets/dir/ascend'
6
7
 
7
- require 'qed/reporter/dotprogress'
8
- require 'qed/reporter/summary'
9
- require 'qed/reporter/verbatim'
8
+ require 'qed/evaluator'
10
9
 
11
10
  #Assertion = AE::Assertion
12
- Expectation = Assertor
13
-
14
- # Global Before
15
- def self.Before(&procedure)
16
- @_before = procedure if procedure
17
- @_before
18
- end
19
-
20
- # Global After
21
- def self.After(&procedure)
22
- @_after = procedure if procedure
23
- @_after
24
- end
25
-
26
- # New Specification
27
- #def initialize(specs, output=nil)
28
- # @specs = [specs].flatten
29
- #end
11
+ # Expectation = Assertor
30
12
 
31
13
  # = Script
32
14
  #
15
+ # When run current working directory is changed to that of
16
+ # the demonstration script's. So any relative file references
17
+ # within a demo must take that into account.
18
+ #
33
19
  class Script
34
20
 
35
- #def self.load(file, output=nil)
36
- # new(File.read(file), output)
37
- #end
38
-
39
- # Path of demonstration script.
21
+ # Demonstration file.
40
22
  attr :file
41
23
 
42
- # Reporter object to issue output calls.
43
- attr :output
44
-
45
- # List of helper scripts to require.
46
- attr :helpers
47
-
48
- # New Script
49
- def initialize(file, output=nil)
50
- @file = file
51
- @output = output || Reporter::Verbatim.new #(self)
52
- parse_document(file)
53
- end
54
-
55
- # File basename less extension.
56
- def name
57
- @name ||= File.basename(file).chomp(File.extname(file))
58
- end
59
-
60
- #
61
- def directory
62
- @directory ||= Dir.pwd #File.dirname(File.expand_path(file))
63
- end
64
-
65
- #def convert
66
- # @source.gsub(/^\w/, '# \1')
67
- #end
68
-
69
- # Run the script.
70
- def run
71
- @lineno = 0
24
+ # Expanded dirname of +file+.
25
+ attr :dir
72
26
 
73
- $LOAD_PATH.unshift(directory)
74
-
75
- import_helpers
76
-
77
- steps.each do |step|
78
- output.report_step(step)
79
- case step
80
- when /^[=#]/
81
- output.report_header(step)
82
- when /^\S/
83
- output.report_comment(step)
84
- context.When.each do |(regex, proc)|
85
- if md = regex.match(step)
86
- proc.call(*md[1..-1])
87
- end
88
- end
89
- else
90
- #if context.table
91
- # run_table(step)
92
- #else
93
- run_step(step)
94
- #end
95
- end
96
- @lineno += step.count("\n")
97
- end
98
-
99
- $LOAD_PATH.index(directory){ |i| $LOAD_PATH.delete_at(i) }
100
- end
101
-
102
- #--
103
- # NOTE: The Around code is in place should we decide
104
- # to use it. I'm not sure yet if it's really neccessary,
105
- # since we have Before and After.
106
- #++
107
- def run_step(step=nil, &blk)
108
- QED.Before.call if QED.Before
109
- context.Before.call if context.Before
110
- begin
111
- if blk # TODO: Is this still used?
112
- blk.call #eval(step, context._binding)
113
- else
114
- #if context.Around
115
- # context.Around.call do
116
- # eval(step, context._binding, @file, @lineno+1)
117
- # end
118
- #else
119
- eval(step, context._binding, @file, @lineno+1)
120
- #end
121
- end
122
- output.report_pass(step) if step
123
- rescue Assertion => error
124
- output.report_fail(step, error)
125
- rescue Exception => error
126
- output.report_error(step, error)
127
- ensure
128
- context.After.call if context.After
129
- QED.After.call if QED.After
130
- end
131
- end
132
-
133
- =begin
134
27
  #
135
- def run_table(step)
136
- file = context.table
137
- Dir.ascend(Dir.pwd) do |path|
138
- f1 = File.join(path, file)
139
- f2 = File.join(path, 'fixtures', file)
140
- fr = File.file?(f1) ? f1 : File.exist?(f2) ? f2 : nil
141
- (file = fr; break) if fr
142
- end
143
- output.report_pass(step) #step)
144
-
145
- tbl = YAML.load(File.new(file))
146
- key = tbl.shift
147
- tbl.each do |set|
148
- assign = key.zip(set).map{ |k, v| "#{k}=#{v.inspect};" }.join
149
- run_table_step(assign + step, set)
150
- #run_step(set.inspect.tabto(4)){ blk.call(set) }
151
- #@_script.run_step(set.to_yaml.tabto(2)){ blk.call(set) }
152
- #@_script.output.report_table(set)
153
- end
154
- #output.report_pass(step) #step)
155
- context.table = nil
156
- end
157
-
158
- #
159
- #def run_table_step(step, set)
160
- def run_table_step(set, &blk)
161
- context.before.call if context.before
162
- begin
163
- #eval(step, context._binding, @file) # TODO: would be nice to know file and lineno here
164
- blk.call(*set)
165
- output.report_pass(' ' + set.inspect) #step)
166
- rescue Assertion => error
167
- output.report_fail(set.inspect, error)
168
- rescue Exception => error
169
- output.report_error(set.inspect, error)
170
- ensure
171
- context.after.call if context.after
172
- end
173
- end
174
- =end
175
-
176
- # Cut-up script into steps.
177
- def steps
178
- @steps ||= (
179
- code = false
180
- str = ''
181
- steps = []
182
- @source.each_line do |line|
183
- if /^\s*$/.match line
184
- str << line
185
- elsif /^[=]/.match line
186
- steps << str #.chomp("\n")
187
- steps << line #.chomp("\n")
188
- str = ''
189
- #str << line
190
- code = false
191
- elsif /^\S/.match line
192
- if code
193
- steps << str #.chomp("\n")
194
- str = ''
195
- str << line
196
- code = false
197
- else
198
- str << line
199
- end
200
- else
201
- if code
202
- str << line
203
- else
204
- steps << str
205
- str = ''
206
- str << line
207
- code = true
208
- end
209
- end
210
- end
211
- steps << str
212
- #steps.map{ |s| s.chomp("\n") }
213
- steps
214
- )
215
- end
28
+ attr :scope
216
29
 
217
- # The run context.
218
- def context
219
- @context ||= Context.new(self)
30
+ # New Script
31
+ def initialize(file, scope=nil)
32
+ @file = file
33
+ @scope = scope || Scope.new
34
+ apply_environment
220
35
  end
221
36
 
222
37
  #
223
- def import(helper)
224
- code = File.read(helper)
225
- eval(code, context._binding)
38
+ def dir
39
+ @dir ||= File.expand_path(File.dirname(file))
226
40
  end
227
41
 
228
- private
229
-
230
- # Splits the document into main source and footer
231
- # and extract the helper document references from
232
- # the footer.
233
- #
234
- def parse_document(file)
235
- text = File.read(file)
236
- index = text.rindex('---') || text.size
237
- source = text[0...index]
238
- footer = text[index+3..-1].to_s.strip
239
- helpers = parse_helpers(footer)
240
- @source = source
241
- @helpers = helpers
42
+ # File basename less extension.
43
+ def name
44
+ @name ||= File.basename(file).chomp(File.extname(file))
242
45
  end
243
46
 
244
- #
245
- def parse_helpers(footer)
246
- helpers = []
247
- footer.split("\n").each do |line|
248
- next if line.strip == ''
249
- case line
250
- when /\[(.*?)\]\((.*?)\)/
251
- helpers << $2
252
- when /(.*?)\[(.*?)\]/
253
- helpers << $2
254
- end
255
- end
256
- helpers
47
+ # Nokogiri HTML document.
48
+ def document
49
+ @document ||= Nokogiri::HTML(to_html)
257
50
  end
258
51
 
259
- =begin
260
- # Looks for a master +helper.rb+ file and a special
261
- # helpers/<name>.rb file. Both of these, when found, will
262
- # be imported when this script is run.
263
-
264
- def collect_helpers
265
- dir = File.dirname(file)
266
- list = []
267
- list << "helper.rb" if File.exist?(File.join(dir, "helper.rb"))
268
- list << "helpers/#{name}.rb" if File.exist?(File.join(dir, "helpers/#{name}.rb"))
269
- list
52
+ # Root node of the html document.
53
+ def root
54
+ document.root
270
55
  end
271
- =end
272
-
273
- # TODO: How to determine where to find the env.rb file?
274
- #def require_environment
275
- # dir = File.dirname(file)
276
- # dir = File.expand_path(dir)
277
- # env = loop do
278
- # file = File.join(dir, 'env.rb')
279
- # break file if File.exist?(file)
280
- # break nil if ['demo', 'demos', 'doc', 'docs', 'test', 'tests'].include? File.basename(dir)
281
- # break nil if dir == Dir.pwd
282
- # dir = File.dirname(dir)
283
- # end
284
- # require(env) if env
285
- #end
286
56
 
287
- # FIXME: where to stop looking for helpers.
288
- def import_helpers
289
- hlp = []
290
- dir = Dir.pwd #File.expand_path(dir)
291
- env = loop do
292
- helpers.each do |helper|
293
- file = File.join(dir, 'helpers', helper)
294
- if File.exist?(file)
295
- hlp << file
296
- end
297
- end
298
- break if ['qed', 'demo', 'demos', 'doc', 'docs', 'test', 'tests'].include? File.basename(dir)
299
- dir = File.dirname(dir)
57
+ # Open file and translate template into HTML text.
58
+ def to_html
59
+ #case file
60
+ #when /^http/
61
+ # ext = File.extname(file).sub('.','')
62
+ # Tilt[ext].new{ source }
63
+ #else
64
+ #end
65
+ if File.extname(file) == '.html'
66
+ File.read(file)
67
+ else
68
+ Tilt.new(file).render
300
69
  end
301
- hlp.each{ |helper| import(helper) }
302
- end
303
-
304
- end
305
-
306
- #
307
- class Context < Module
308
-
309
- TABLE = /^TABLE\[(.*?)\]/i
310
-
311
- def initialize(script)
312
- @_script = script
313
- @_when = []
314
- @_tables = []
315
- end
316
-
317
- def _binding
318
- @_binding ||= binding
319
- end
320
-
321
- # Before each step.
322
- def Before(&procedure)
323
- @_before = procedure if procedure
324
- @_before
325
70
  end
326
71
 
327
- # After each step.
328
- def After(&procedure)
329
- @_after = procedure if procedure
330
- @_after
72
+ # Open, convert to HTML and cache.
73
+ def html
74
+ @html ||= to_html
331
75
  end
332
76
 
333
- # Run code around each step.
334
77
  #
335
- # Around procedures must take a block, in which the step is run.
336
- #
337
- # Around do |&step|
338
- # ... do something here ...
339
- # step.call
340
- # ... do stiff stuff ...
341
- # end
342
- #
343
- #def Around(&procedure)
344
- # @_around = procedure if procedure
345
- # @_around
78
+ #def source
79
+ # @source ||= (
80
+ # #case file
81
+ # #when /^http/
82
+ # # ext = File.extname(file).sub('.','')
83
+ # # open(file)
84
+ # #else
85
+ # File.read(file)
86
+ # #end
87
+ # )
346
88
  #end
347
89
 
348
- # Comment match procedure.
349
90
  #
350
- # This is useful for creating unobtrusive setup and (albeit more
351
- # limited) teardown code. A pattern is matched against each comment
352
- # as it is processed. If there is match, the code procedure is
353
- # triggered, passing in any mathcing expression arguments.
354
- #
355
- def When(pattern=nil, &procedure)
356
- return @_when unless procedure
357
- raise ArgumentError unless pattern
358
- unless Regexp === pattern
359
- pattern = __when_string_to_regexp(pattern)
360
- end
361
- @_when << [pattern, procedure]
91
+ def run(*observers)
92
+ evaluator = Evaluator.new(self, *observers)
93
+ evaluator.run
362
94
  end
363
95
 
364
- # Code match-and-transform procedure.
365
- #
366
- # This is useful to transform human readable code examples
367
- # into proper exectuable code. For example, say you want to
368
- # run shell code, but want to make if look like typical
369
- # shelle examples:
370
- #
371
- # $ cp fixture/a.rb fixture/b.rb
372
- #
373
- # You can use a transform to convert lines starting with '$'
374
- # into executable Ruby using #system.
375
96
  #
376
- # system('cp fixture/a.rb fixture/b.rb')
377
- #
378
- #def Transform(pattern=nil, &procedure)
379
- #
380
- #end
381
-
382
- # Table-based steps.
383
- def Table(file=nil, &blk)
384
- file = file || @_tables.last
385
- tbl = YAML.load(File.new(file))
386
- tbl.each do |set|
387
- blk.call(*set)
388
- end
389
- @_tables << file
97
+ def environment
98
+ glob = File.join(dir, '{environment,common,shared}', '*')
99
+ Dir[glob]
390
100
  end
391
101
 
392
- # Read/Write a fixture.
393
- def Data(file, &content)
394
- raise if File.directory?(file)
395
- if content
396
- FileUtils.mkdir_p(File.dirname(fname))
397
- case File.extname(file)
398
- when '.yml', '.yaml'
399
- File.open(file, 'w'){ |f| f << content.call.to_yaml }
400
- else
401
- File.open(file, 'w'){ |f| f << content.call }
402
- end
403
- else
404
- #raise LoadError, "no such fixture file -- #{fname}" unless File.exist?(fname)
102
+ #
103
+ def apply_environment
104
+ environment.each do |file|
405
105
  case File.extname(file)
406
- when '.yml', '.yaml'
407
- YAML.load(File.new(file))
106
+ when '.rb'
107
+ eval(File.read(file), scope.__binding__, file)
408
108
  else
409
- File.read(file)
109
+ Script.new(file, scope).run
410
110
  end
411
111
  end
412
112
  end
413
113
 
414
- private
415
-
416
- def __when_string_to_regexp(str)
417
- str = str.split(/(\(\(.*?\)\))(?!\))/).map{ |x|
418
- x =~ /\A\(\((.*)\)\)\z/ ? $1 : Regexp.escape(x)
419
- }.join
420
- str = str.gsub(/(\\\ )+/, '\s+')
421
- Regexp.new(str, Regexp::IGNORECASE)
422
-
423
- #rexps = []
424
- #str = str.gsub(/\(\((.*?)\)\)/) do |m|
425
- # rexps << '(' + $1 + ')'
426
- # "\0"
427
- #end
428
- #str = Regexp.escape(str)
429
- #rexps.each do |r|
430
- # str = str.sub("\0", r)
431
- #end
432
- #str = str.gsub(/(\\\ )+/, '\s+')
433
- #Regexp.new(str, Regexp::IGNORECASE)
434
- end
435
-
436
- #
437
- # check only local and maybe start paths
438
- #def __locate_file(file)
439
- # Dir.ascend(Dir.pwd) do |path|
440
- # f1 = File.join(path, file)
441
- # f2 = File.join(path, 'fixtures', file)
442
- # fr = File.file?(f1) ? f1 : File.exist?(f2) ? f2 : nil
443
- # (file = fr; break) if fr
444
- # end
445
- #end
446
-
447
114
  end
448
115
 
449
116
  end