qed 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/History.rdoc +15 -0
  2. data/eg/hello_world.rdoc +15 -0
  3. data/eg/view_error.rdoc +21 -0
  4. data/eg/website.rdoc +12 -0
  5. data/lib/qed/advice.rb +4 -3
  6. data/lib/qed/command.rb +2 -3
  7. data/lib/qed/core_ext/instance_exec.rb +36 -0
  8. data/lib/qed/demo.rb +0 -1
  9. data/lib/qed/evaluator.rb +75 -36
  10. data/lib/qed/extensions/filefixtures.rb +27 -0
  11. data/lib/qed/extensions/shell_session.rb +2 -0
  12. data/lib/qed/meta/data.rb +29 -0
  13. data/lib/qed/meta/gemfile +10 -0
  14. data/{PROFILE → lib/qed/meta/profile} +0 -0
  15. data/lib/qed/parser.rb +148 -74
  16. data/lib/qed/reporter/abstract.rb +175 -25
  17. data/lib/qed/reporter/bullet.rb +14 -10
  18. data/lib/qed/reporter/dotprogress.rb +26 -9
  19. data/lib/qed/reporter/verbatim.rb +36 -15
  20. data/lib/qed/scope.rb +18 -8
  21. data/lib/qed/session.rb +2 -2
  22. data/meta/data.rb +29 -0
  23. data/meta/gemfile +10 -0
  24. data/{lib/qed/profile.yml → meta/profile} +0 -0
  25. data/{demo → qed}/01_demos.rdoc +7 -13
  26. data/{demo → qed}/02_advice.rdoc +7 -7
  27. data/{demo → qed}/03_helpers.rdoc +2 -2
  28. data/{demo → qed}/04_samples.rdoc +0 -0
  29. data/{demo → qed}/05_quote.rdoc +0 -0
  30. data/{demo → qed}/07_toplevel.rdoc +0 -0
  31. data/{demo → qed}/08_cross_script.rdoc +0 -2
  32. data/{demo → qed}/09_cross_script.rdoc +5 -3
  33. data/{demo → qed}/10_constant_lookup.rdoc +0 -0
  34. data/{demo → qed}/applique/constant.rb +0 -0
  35. data/{demo → qed}/applique/env.rb +0 -0
  36. data/{demo → qed}/applique/fileutils.rb +0 -0
  37. data/{demo → qed}/applique/markup.rb +0 -0
  38. data/{demo → qed}/applique/quote.rb +0 -0
  39. data/{demo → qed}/applique/toplevel.rb +0 -0
  40. data/{demo → qed}/helpers/advice.rb +0 -0
  41. data/{demo → qed}/helpers/sample.rb +0 -0
  42. data/{demo → qed}/helpers/toplevel.rb +0 -0
  43. data/{demo → qed}/samples/data.txt +0 -0
  44. data/{demo → qed}/samples/table.yml +0 -0
  45. metadata +43 -38
  46. data/REQUIRE +0 -7
  47. data/VERSION +0 -5
  48. data/lib/qed/package.yml +0 -5
  49. data/script/qedoc +0 -2
  50. data/script/test +0 -4
@@ -1,5 +1,20 @@
1
1
  = RELEASE HISTORY
2
2
 
3
+ == 2.4.0 / 2010-09-02
4
+
5
+ All engines go! QED has not been tested against 1.8.6, 1.8.7 and 1.9.2.
6
+ Underthehood steps are not organized in doubely-linked lists, which makes
7
+ them much more robust and flexible. This release also improves scoping,
8
+ test counts, and inline documentation parsing.
9
+
10
+ Changes:
11
+
12
+ * Use new doubly-linked list step design.
13
+ * Fix -r option on command line.
14
+ * Provide #instance_exec core extension for Ruby 1.8.6.
15
+ * Scope is extended by and includes applique.
16
+
17
+
3
18
  == 2.3.0 / 2010-07-14
4
19
 
5
20
  Bug to the scurry! QED has broken through the code/document ceiling and
@@ -0,0 +1,15 @@
1
+ = Hello World
2
+
3
+ Did you know that famous `Hello World` moniker is
4
+ eleven characters long?
5
+
6
+ "Hello World".size.assert == 11
7
+
8
+ To pass a piece of literal text on with a description
9
+ we simply need to end it with a ...
10
+
11
+ Now this text will appear verbatim.
12
+ In the applique arguments.
13
+
14
+ That's all.
15
+
@@ -0,0 +1,21 @@
1
+ = Examples of Failure
2
+
3
+ This document is here simply to demonstrate what
4
+ a failed and error raising code steps looks like.
5
+
6
+ When run with the -v (verbatim) option, for instance, +qed+
7
+ will highlight the following sections in red and give a brief
8
+ error message.
9
+
10
+ == Failure
11
+
12
+ This step demonstrates a failed assertion.
13
+
14
+ 1.assert == 2
15
+
16
+ == Error
17
+
18
+ This step demonstrates a raised error.
19
+
20
+ raise "Just because"
21
+
@@ -0,0 +1,12 @@
1
+ = Addition
2
+
3
+ require 'calculator'
4
+ calculator = Caclulater.new
5
+
6
+ A Calculator can add two numbers.
7
+
8
+ calculator.push 2
9
+ calculator.push 2
10
+ calculator.add
11
+ calculator.output.assert == 4
12
+
@@ -1,3 +1,5 @@
1
+ require 'qed/core_ext/instance_exec'
2
+
1
3
  module QED
2
4
 
3
5
  # = Advice
@@ -12,8 +14,7 @@ module QED
12
14
  #
13
15
  # == Pattern Matchers (When)
14
16
  #
15
- # Matchers are evaluated when they match a blocks
16
- # commentary.
17
+ # Matchers are evaluated when they match a description.
17
18
  #
18
19
  # == Event Signals (Before, After)
19
20
  #
@@ -67,7 +68,7 @@ module QED
67
68
 
68
69
  #
69
70
  def call_matchers(scope, section)
70
- match = section.commentary
71
+ match = section.text
71
72
  args = section.arguments
72
73
  matchers.each do |(patterns, proc)|
73
74
  compare = match
@@ -134,7 +134,7 @@ module QED
134
134
  @options[:loadpath] ||= []
135
135
  @options[:loadpath].concat(arg.split(/[:;]/).map{ |dir| File.expand_path(dir) })
136
136
  end
137
- opt.on('--require', "-r", "require library") do |arg|
137
+ opt.on('--require', "-r LIB", "require library") do |arg|
138
138
  @options[:requires] ||= []
139
139
  @options[:requires].concat(arg.split(/[:;]/)) #.map{ |dir| File.expand_path(dir) })
140
140
  end
@@ -207,7 +207,7 @@ module QED
207
207
  end
208
208
  end
209
209
  files = files.flatten.uniq
210
- files.map{|f| File.expand_path(f) }.sort
210
+ files.map{|f| File.expand_path(f) }.uniq.sort
211
211
  end
212
212
 
213
213
  # Parse command-line options along with profile options.
@@ -349,4 +349,3 @@ module QED
349
349
  end
350
350
 
351
351
  end
352
-
@@ -0,0 +1,36 @@
1
+ class Object
2
+
3
+ unless method_defined?(:instance_exec) # 1.9
4
+ require 'thread'
5
+
6
+ module InstanceExecMethods #:nodoc:
7
+ end
8
+
9
+ include InstanceExecMethods
10
+
11
+ # Evaluate the block with the given arguments within the context of
12
+ # this object, so self is set to the method receiver.
13
+ #
14
+ # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
15
+ #
16
+ # This version has been borrowed from Rails for compatibility sake.
17
+ def instance_exec(*args, &block)
18
+ begin
19
+ old_critical, Thread.critical = Thread.critical, true
20
+ n = 0
21
+ n += 1 while respond_to?(method_name = "__instance_exec#{n}")
22
+ InstanceExecMethods.module_eval { define_method(method_name, &block) }
23
+ ensure
24
+ Thread.critical = old_critical
25
+ end
26
+
27
+ begin
28
+ send(method_name, *args)
29
+ ensure
30
+ InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -93,4 +93,3 @@ module QED
93
93
  end
94
94
 
95
95
  end
96
-
@@ -8,7 +8,7 @@ module QED
8
8
  #
9
9
  def initialize(script, *observers)
10
10
  @script = script
11
- @ast = script.parse
11
+ @steps = script.parse
12
12
 
13
13
  #@file = script.file
14
14
  #@scope = script.scope
@@ -21,46 +21,85 @@ module QED
21
21
  #
22
22
  def run
23
23
  advise!(:before_demo, @script)
24
- process
24
+ advise!(:demo, @script)
25
+ run_steps
25
26
  advise!(:after_demo, @script)
26
27
  end
27
28
 
28
29
  #
29
- def process
30
- @ast.each do |section|
31
- evaluate(section)
30
+ def run_steps #process
31
+ @steps.each do |step|
32
+ evaluate(step)
32
33
  end
33
34
  end
34
35
 
35
- # Evaluate a demo section.
36
- def evaluate(section)
37
- advise!(:text, section) # TODO: rename to :step?
38
- evaluate_links(section)
39
- advise!(:before_step, section, @script.file)
36
+ #
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
56
+
57
+ #
58
+ def evaluate_head(step)
59
+ advise!(:head, step)
60
+ end
61
+
62
+ #
63
+ def evaluate_desc(step)
64
+ evaluate_links(step)
40
65
  begin
41
- advise!(:when, section)
42
- # TODO: how to handle catching asserts in advice?
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
+ error!(step, exception)
74
+ else
75
+ pass!(step)
43
76
  end
44
- if section.code?
45
- begin
46
- advise!(:code, section)
47
- @script.evaluate(section.eval_code, section.lineno)
48
- rescue SystemExit
49
- pass!(section)
50
- rescue Assertion => exception
51
- fail!(section, exception)
52
- rescue Exception => exception
53
- error!(section, exception)
54
- else
55
- pass!(section)
56
- end
77
+ end
78
+
79
+ #
80
+ def evaluate_data(step)
81
+ advise!(:data, step)
82
+ end
83
+
84
+ # Evaluate a demo step.
85
+ def evaluate_code(step)
86
+ begin
87
+ advise!(:code, step)
88
+ @script.evaluate(step.code, step.lineno)
89
+ rescue SystemExit
90
+ pass!(step)
91
+ rescue Assertion => exception
92
+ fail!(step, exception)
93
+ rescue Exception => exception
94
+ error!(step, exception)
95
+ else
96
+ pass!(step)
57
97
  end
58
- advise!(:after_step, section, @script.file)
59
98
  end
60
99
 
61
- # TODO: Not sure how to handle loading links in comment mode.
62
- def evaluate_links(section)
63
- section.commentary.scan(/\[qed:\/\/(.*?)\]/) do |match|
100
+ # TODO: Not sure how to handle loading links in --comment runner mode.
101
+ def evaluate_links(step)
102
+ step.text.scan(/\[qed:\/\/(.*?)\]/) do |match|
64
103
  file = $1
65
104
  # relative to demo script
66
105
  if File.exist?(File.join(@script.directory,file))
@@ -77,25 +116,25 @@ module QED
77
116
  end
78
117
 
79
118
  #
80
- def pass!(section)
81
- advise!(:pass, section)
119
+ def pass!(step)
120
+ advise!(:pass, step)
82
121
  end
83
122
 
84
123
  #
85
- def fail!(section, exception)
86
- advise!(:fail, section, exception)
124
+ def fail!(step, exception)
125
+ advise!(:fail, step, exception)
87
126
  #raise exception
88
127
  end
89
128
 
90
129
  #
91
- def error!(section, exception)
92
- advise!(:error, section, exception)
130
+ def error!(step, exception)
131
+ advise!(:error, step, exception)
93
132
  #raise exception
94
133
  end
95
134
 
96
135
  #
97
136
  def import!(file)
98
- advise!(:unload)
137
+ advise!(:unload) # should this also occur just befor after_demo ?
99
138
  eval(File.read(file), @script.binding, file)
100
139
  advise!(:load, file)
101
140
  end
@@ -0,0 +1,27 @@
1
+ # This extension provides a simple means for
2
+ # create file-system fixtures.
3
+
4
+ require 'erb'
5
+
6
+ # Set global temporary directory.
7
+ $tmpdir = 'tmp'
8
+
9
+ #
10
+ def copy_fixture(name, tmpdir=$tmpdir)
11
+ FileUtils.mkdir(tmpdir)
12
+ srcdir = File.join(demo_directory, 'fixtures', name)
13
+ paths = Dir.glob(File.join(srcdir, '**', '*'), File::FNM_DOTMATCH)
14
+ paths.each do |path|
15
+ basename = File.basename(path)
16
+ next if basename == '.'
17
+ next if basename == '..'
18
+ dest = File.join(tmpdir, path.sub(srcdir+'/', ''))
19
+ if File.directory?(path)
20
+ FileUtils.mkdir(dest)
21
+ else
22
+ text = ERB.new(File.read(path)).result
23
+ File.open(dest, 'w'){ |f| f << text }
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,2 @@
1
+ # This extension provides a basic means for testing
2
+ # shell commands.
@@ -0,0 +1,29 @@
1
+ Object.__send__(:remove_const, :VERSION) if Object.const_defined?(:VERSION) # becuase Ruby 1.8~ gets in the way
2
+
3
+ module QED
4
+
5
+ def self.__DIR__
6
+ File.dirname(__FILE__)
7
+ end
8
+
9
+ def self.gemfile
10
+ @gemfile ||= (
11
+ require 'yaml'
12
+ YAML.load(File.new(__DIR__ + '/gemfile'))
13
+ )
14
+ end
15
+
16
+ def self.profile
17
+ @profile ||= (
18
+ require 'yaml'
19
+ YAML.load(File.new(__DIR__ + '/profile'))
20
+ )
21
+ end
22
+
23
+ def self.const_missing(name)
24
+ key = name.to_s.downcase
25
+ gemfile[key] || profile[key] || super(name)
26
+ end
27
+
28
+ end
29
+
@@ -0,0 +1,10 @@
1
+ name: qed
2
+ version: 2.4.0
3
+ date: 2010-09-02
4
+
5
+ requires:
6
+ - ansi
7
+ - facets
8
+ - ae (test)
9
+ - syckle (build)
10
+
File without changes
@@ -36,7 +36,7 @@ module QED
36
36
  when :comment
37
37
  parse_comment_lines
38
38
  else
39
- index = -1
39
+ index = 0 #-1
40
40
  File.readlines(file).to_a.map do |line|
41
41
  [index += 1, line]
42
42
  end
@@ -46,25 +46,32 @@ module QED
46
46
  # TODO: It would be nice if we could get ther require statement for the
47
47
  # comment mode to be relative to an actual loadpath.
48
48
  def parse_comment_lines
49
- omit = false
49
+ ruby_omit = false
50
+ rdoc_omit = false
50
51
  lines = [
51
52
  [0, "Load #{File.basename(file)} script.\n"],
52
53
  [0, "\n"],
53
54
  [0, " require '#{file}'\n"]
54
55
  ]
55
- index = 0
56
+ index = 1
56
57
  File.readlines(file).each do |l|
57
58
  case l
59
+ when /^=begin(?!\s+qed)/
60
+ ruby_omit = true
61
+ when /^=end/
62
+ ruby_omit = false
58
63
  when /^\s*\#\-\-\s*$/
59
- omit = true
64
+ rdoc_omit = true
60
65
  when /^\s*\#\+\+\s*$/
61
- omit = false
62
- when /^\s*\#\ \-\-/ # ?
63
- # -- skip internal comments
64
- when /^\s*\#/
65
- lines << [index, l.lstrip.sub(/^\#\ ?/, '')] unless omit
66
+ rdoc_omit = false
67
+ ##when /^\s*\#\ \-\-/ # not needed just double comment
68
+ ## # -- skip internal comments
69
+ when /^\s*##/
70
+ ## skip internal comments
71
+ when /^\s*\#/
72
+ lines << [index, l.lstrip.sub(/^\#\ ?/, '')] unless (ruby_omit or rdoc_omit)
66
73
  else
67
- lines << [index, "\n"] unless lines.last[1] == "\n"
74
+ lines << [index, "\n"] unless lines.last[1] == "\n" unless (ruby_omit or rdoc_omit)
68
75
  end
69
76
  index += 1
70
77
  end
@@ -135,33 +142,37 @@ module QED
135
142
 
136
143
  def parse
137
144
  tree = []
138
- mode = :rem
145
+ flush = true
139
146
  pend = false
140
- block = Block.new
147
+ block = Block.new(file)
141
148
  lines.each do |lineno, line|
142
149
  case line
143
150
  when /^\s*$/
144
- case mode
145
- when :rem
146
- pend = true unless line == 0
147
- block.rem << [lineno, line]
148
- when :raw
151
+ if flush
152
+ pend = true unless lineno == 0
153
+ block.raw << [lineno, line]
154
+ else
149
155
  block.raw << [lineno, line]
150
156
  end
151
157
  when /\A\s+/
152
- mode = :raw
158
+ if flush
159
+ tree << block.ready!(flush, tree.last)
160
+ block = Block.new(file)
161
+ end
162
+ pend = false
163
+ flush = false
153
164
  block.raw << [lineno, line]
154
165
  else
155
- if pend || mode == :raw
156
- pend = false
157
- mode = :rem
158
- tree << block.ready!
159
- block = Block.new
166
+ if pend || !flush
167
+ tree << block.ready!(flush, tree.last)
168
+ pend = false
169
+ flush = true
170
+ block = Block.new(file)
160
171
  end
161
- block.rem << [lineno, line]
172
+ block.raw << [lineno, line]
162
173
  end
163
174
  end
164
- tree << block.ready!
175
+ tree << block.ready!(flush, tree.last)
165
176
  @ast = tree
166
177
  end
167
178
 
@@ -176,97 +187,160 @@ module QED
176
187
 
177
188
  # Section Block
178
189
  class Block
179
- # Block commentary.
180
- attr :rem
181
-
182
190
  # Block raw code/text.
183
191
  attr :raw
184
192
 
193
+ # previous block
194
+ attr :back_step
195
+
196
+ # next block
197
+ attr :next_step
198
+
185
199
  #
186
- def initialize
187
- @rem = []
188
- @raw = []
189
- @has_code = true
200
+ def initialize(file)
201
+ @file = file
202
+ @raw = []
203
+ @type = :description
204
+ @back_step = nil
205
+ @next_step = nil
190
206
  end
191
207
 
192
208
  #
193
- def ready!
194
- @commentary = rem.map{ |lineno, line| line }.join
195
- @example = raw.map{ |lineno, line| line }.join
196
- @has_code = false if @raw.empty?
197
- @has_code = false if continuation?
209
+ def ready!(flush, back_step)
210
+ @flush = flush
211
+ @back_step = back_step
212
+
213
+ @text = raw.map{ |lineno, line| line }.join
214
+ @type = parse_type
215
+
216
+ @back_step.next_step = self if @back_step
217
+
198
218
  self
199
219
  end
200
220
 
201
221
  #
202
- def commentary
203
- @commentary
222
+ def to_s
223
+ case type
224
+ when :description
225
+ text
226
+ else
227
+ text
228
+ end
229
+ end
230
+
231
+ #
232
+ def text
233
+ @text
204
234
  end
205
235
 
206
236
  #
207
- def example
208
- @example
237
+ def flush?
238
+ @flush
209
239
  end
210
240
 
211
241
  # Returns an Array of prepared example text
212
242
  # for use in advice.
213
243
  def arguments
214
- continuation? ? [example_argument] : []
244
+ if next_step && next_step.data?
245
+ [next_step.sample_text]
246
+ else
247
+ []
248
+ end
215
249
  end
216
250
 
217
- #
218
- def code?
219
- @has_code
251
+ # What type of block is this?
252
+ def type
253
+ @type
220
254
  end
221
255
 
222
- # First line of example text.
223
- def lineno
224
- @line ||= @raw.first.first
225
- end
256
+ #
257
+ def head? ; @type == :head ; end
226
258
 
227
259
  #
228
- def code
229
- @example
230
- end
260
+ def desc? ; @type == :desc ; end
231
261
 
232
262
  #
233
- def eval_code
234
- @eval_code ||= tweak_code
263
+ def code? ; @type == :code ; end
264
+
265
+ # Any commentary ending in `...` or `:` will mark the following
266
+ # block as a plain text *sample* and not example code to be evaluated.
267
+ def data? ; @type == :data ; end
268
+
269
+ #
270
+ alias_method :header?, :head?
271
+
272
+ #
273
+ alias_method :description?, :desc?
274
+
275
+
276
+ # First line of example text.
277
+ def lineno
278
+ @line ||= @raw.first.first
235
279
  end
236
280
 
237
281
  #
238
- def tweak_code
239
- code = example.dup
240
- code.gsub!(/\n\s*\#\ ?\=\>/, '.assert == ')
241
- code.gsub!(/\s*\#\ ?\=\>/, '.assert == ')
242
- code
282
+ def code
283
+ @code ||= tweak_code
243
284
  end
244
285
 
245
286
  # Clean up the example text, removing unccesseary white lines
246
287
  # and triple quote brackets, but keep indention intact.
247
- def clean_example
248
- text = example.chomp.sub(/\A\n/,'')
249
- if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
250
- text = md[1]
288
+ def clean_text
289
+ str = text.chomp.sub(/\A\n/,'')
290
+ if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str)
291
+ str = md[1]
251
292
  end
252
- text.rstrip
293
+ str.rstrip
253
294
  end
254
295
 
255
- # When the example is raw text and passed to an adivce block, this
296
+ # When the text is sample text and passed to an adivce block, this
256
297
  # provides the prepared form of the example text, removing white lines,
257
298
  # triple quote brackets and indention.
258
- def example_argument
259
- text = example.tabto(0).chomp.sub(/\A\n/,'')
260
- if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
261
- text = md[1]
299
+ def sample_text
300
+ str = text.tabto(0).chomp.sub(/\A\n/,'')
301
+ if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str)
302
+ str = md[1]
303
+ end
304
+ str.rstrip
305
+ end
306
+
307
+ # TODO: object_hexid
308
+ def inspect
309
+ %[#<Block:#{object_id} "#{text[0..25]} ...">]
310
+ end
311
+
312
+ protected
313
+
314
+ #
315
+ def next_step=(n)
316
+ @next_step = n
317
+ end
318
+
319
+ private
320
+
321
+ #
322
+ def parse_type
323
+ if flush?
324
+ if /\A[=#]/ =~ text
325
+ :head
326
+ else
327
+ :desc
328
+ end
329
+ else
330
+ if back_step && /(\.\.\.|\:)\s*\Z/m =~ back_step.text.strip
331
+ :data
332
+ else
333
+ :code
334
+ end
262
335
  end
263
- text.rstrip
264
336
  end
265
337
 
266
- # And commentary ending in `...` or `:` will mark the following
267
- # example as plain text and not code to be evaluated.
268
- def continuation?
269
- /(\.\.\.|\:)\s*\Z/m =~ commentary
338
+ #
339
+ def tweak_code
340
+ code = text.dup
341
+ code.gsub!(/\n\s*\#\ ?\=\>/, '.assert = ')
342
+ code.gsub!(/\s*\#\ ?\=\>/, '.assert = ')
343
+ code
270
344
  end
271
345
 
272
346
  end