qed 1.3 → 2.0.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.
@@ -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