qed 2.5.0 → 2.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/.ruby ADDED
@@ -0,0 +1,47 @@
1
+ ---
2
+ spec_version: 1.0.0
3
+ replaces: []
4
+
5
+ loadpath:
6
+ - lib
7
+ name: qed
8
+ repositories:
9
+ public: git://github.com/proutils/qed.git
10
+ conflicts: []
11
+
12
+ engine_check: []
13
+
14
+ title: QED
15
+ resources:
16
+ home: http://proutils.github.com/qed
17
+ work: http://github.com/proutils/qed
18
+ maintainers: []
19
+
20
+ requires:
21
+ - group: []
22
+
23
+ name: ansi
24
+ version: 0+
25
+ - group: []
26
+
27
+ name: facets
28
+ version: 2.8+
29
+ - group: []
30
+
31
+ name: ae
32
+ version: 0+
33
+ - group:
34
+ - build
35
+ name: syckle
36
+ version: 0+
37
+ manifest: Manifest
38
+ version: 2.5.1
39
+ licenses: []
40
+
41
+ copyright: Copyright (c) 2006 Thomas Sawyer
42
+ authors:
43
+ - Thomas Sawyer <transfire@gmail.com>
44
+ organization: RubyWorks
45
+ description: QED (Quality Ensured Demonstrations) is a TDD/BDD framework utilizing Literate Programming techniques.
46
+ summary: Quod Erat Demonstrandum
47
+ created: 2006-12-16
data/bin/qed CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'qed'
3
- QED.main(*ARGV)
3
+ QED.cli(*ARGV)
4
4
 
data/lib/qed/demo.rb CHANGED
@@ -17,19 +17,25 @@ module QED
17
17
  # Parser mode.
18
18
  attr :mode
19
19
 
20
+ # Working directory.
21
+ attr :cwd
22
+
20
23
  # Scope to run demonstration within. (Known as a "World" in Cucumber.)
21
24
  attr :scope
22
25
 
23
26
  # New Script
24
27
  def initialize(file, options={})
25
- @file = file
26
- @scope = options[:scope] || Scope.new(applique, file)
27
- @mode = options[:mode]
28
- @binding = @scope.__binding__
28
+ @file = file
29
+ @mode = options[:mode]
30
+ @cwd = options[:at] || fallback_cwd
31
+
32
+ @scope = options[:scope] || Scope.new(applique, cwd, file)
33
+
34
+ @binding = @scope.__binding__
29
35
  #apply_environment
30
36
  end
31
37
 
32
- # One binding per script.
38
+ # One binding per demo.
33
39
  def binding
34
40
  @binding #||= @scope.__binding__
35
41
  end
@@ -46,8 +52,8 @@ module QED
46
52
 
47
53
  #
48
54
  def evaluate(code, line)
49
- eval(code, @binding, @file, line)
50
- #@scope.module_eval(section.text, @file, section.line)
55
+ #eval(code, @binding, @file, line)
56
+ @scope.eval(code, @file, line)
51
57
  end
52
58
 
53
59
  # Returns a cached Array of Applique modules.
@@ -105,6 +111,11 @@ module QED
105
111
  # )
106
112
  #end
107
113
 
114
+ # This shouldn't be needed, but is here as a precaution.
115
+ def fallback_cwd
116
+ @dir ||= File.join(Dir.tmpdir, 'qed', File.filename(Dir.pwd), Time.new.strftime("%Y%m%d%H%M%S"))
117
+ end
118
+
108
119
  end
109
120
 
110
121
  end
data/lib/qed/evaluator.rb CHANGED
@@ -67,10 +67,14 @@ module QED
67
67
  advise!(:when, step) # triggers matchers
68
68
  rescue SystemExit
69
69
  pass!(step)
70
- rescue Assertion => exception
71
- fail!(step, exception)
70
+ #rescue Assertion => exception
71
+ # fail!(step, exception)
72
72
  rescue Exception => exception
73
- error!(step, exception)
73
+ if exception.assertion?
74
+ fail!(step, exception)
75
+ else
76
+ error!(step, exception)
77
+ end
74
78
  else
75
79
  pass!(step)
76
80
  end
@@ -78,7 +82,22 @@ module QED
78
82
 
79
83
  #
80
84
  def evaluate_data(step)
81
- advise!(: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)
100
+ end
82
101
  end
83
102
 
84
103
  # Evaluate a demo step.
@@ -87,11 +106,15 @@ module QED
87
106
  advise!(:code, step)
88
107
  @script.evaluate(step.code, step.lineno)
89
108
  rescue SystemExit
90
- pass!(step)
91
- rescue Assertion => exception
92
- fail!(step, exception)
109
+ pass!(step) # TODO: skip!(step)
110
+ #rescue Assertion => exception
111
+ # fail!(step, exception)
93
112
  rescue Exception => exception
94
- error!(step, exception)
113
+ if exception.assertion?
114
+ fail!(step, exception)
115
+ else
116
+ error!(step, exception)
117
+ end
95
118
  else
96
119
  pass!(step)
97
120
  end
@@ -189,7 +189,7 @@ module Reporter
189
189
 
190
190
  # After running a step that raised an error.
191
191
  def error(step, exception)
192
- raise exception if $DEBUG
192
+ raise exception if $DEBUG # TODO: do we really want to do it like this?
193
193
  #@error << [step, exception]
194
194
  end
195
195
 
@@ -237,8 +237,8 @@ module Reporter
237
237
  end
238
238
 
239
239
  def print_tally
240
- assert_count = Assertion.count
241
- assert_fails = Assertion.fails
240
+ assert_count = AE::Assertor.counts[:total]
241
+ assert_fails = AE::Assertor.counts[:fail]
242
242
  assert_delta = assert_count - assert_fails
243
243
 
244
244
  mask = "%s demos, %s steps: %s failures, %s errors (%s/%s assertions)"
@@ -253,7 +253,7 @@ module Reporter
253
253
  end
254
254
 
255
255
  #
256
- INTERNALS = /(lib|bin)[\\\/]qed/
256
+ INTERNALS = /(lib|bin)[\\\/](qed|ae)/
257
257
 
258
258
  =begin
259
259
  # Clean the backtrace of any reference to ko/ paths and code.
@@ -23,8 +23,8 @@ module Reporter #:nodoc:
23
23
 
24
24
  #
25
25
  def data(step)
26
- io.puts step.clean_text.ansi(:blue)
27
- io.puts
26
+ #io.puts step.clean_text.ansi(:blue)
27
+ #io.puts
28
28
  end
29
29
 
30
30
  #
@@ -34,6 +34,8 @@ module Reporter #:nodoc:
34
34
  io.print "#{step.text}".ansi(:green)
35
35
  elsif step.header?
36
36
  io.print "#{step.text}".ansi(:bold)
37
+ elsif step.data?
38
+ io.print "#{step.text}".ansi(:blue)
37
39
  else
38
40
  io.print "#{step.text}"
39
41
  end
@@ -48,7 +50,7 @@ module Reporter #:nodoc:
48
50
  msg = []
49
51
  #msg << ANSI::Code.bold(ANSI::Code.red("FAIL: ")) + error.message
50
52
  #msg << ANSI::Code.bold(clean_backtrace(error.backtrace[0]))
51
- msg << "FAIL: ".ansi(:bold, :red) + error.message #to_str
53
+ msg << "FAIL: ".ansi(:bold, :red) + error.message.to_s #to_str
52
54
  msg << clean_backtrace(error.backtrace[0]).ansi(:bold)
53
55
  io.puts msg.join("\n").tabto(tab||2)
54
56
  io.puts
data/lib/qed/scope.rb CHANGED
@@ -21,9 +21,10 @@ module QED
21
21
  # end
22
22
 
23
23
  #
24
- def initialize(applique, file=nil)
24
+ def initialize(applique, cwd, file=nil)
25
25
  super()
26
26
  @_applique = applique
27
+ @_cwd = cwd
27
28
  @_file = file
28
29
  #@loadlist = []
29
30
 
@@ -61,12 +62,12 @@ module QED
61
62
  end
62
63
 
63
64
  # Evaluate code in the context of the scope's special binding.
64
- def eval(code, binding=nil, file=nil)
65
- super(code, binding || __binding__, @_file)
65
+ # The return result of the evaluation is stored in `@_`.
66
+ def eval(code, file=nil, line=nil)
67
+ @_ = super(code, __binding__, @_file, line)
66
68
  end
67
69
 
68
70
 
69
-
70
71
  # Utilize is like #require, but will evaluate the script in the context
71
72
  # of the current scope.
72
73
  #--
@@ -89,7 +90,6 @@ module QED
89
90
  end
90
91
 
91
92
 
92
-
93
93
  # Define "when" advice.
94
94
  def When(*patterns, &procedure)
95
95
  patterns = patterns.map{ |pat| pat == :text ? :desc : pat }
@@ -113,7 +113,6 @@ module QED
113
113
  end
114
114
 
115
115
 
116
-
117
116
  # TODO: Should Table and Data be extensions that can be loaded if desired?
118
117
 
119
118
  # Use sample table to run steps. The table file will be
@@ -165,6 +164,23 @@ module QED
165
164
  #end
166
165
  end
167
166
 
167
+ # Clear temporary work directory.
168
+ def clear_working_directory!
169
+ dir = @_cwd
170
+ dir = File.expand_path(dir)
171
+
172
+ if dir == '/' or dir == File.expand_path('~')
173
+ abort "DANGER! Trying to use home or root as a temporary directory!"
174
+ end
175
+
176
+ entries = Dir.glob(File.join(dir, '**/*'))
177
+
178
+ dirs, files = entries.partition{ |f| File.directory?(f) }
179
+
180
+ files.each { |file| FileUtils.rm(file) }
181
+ dirs.each { |dir| FileUtils.rmdir(dir) }
182
+ end
183
+
168
184
  #
169
185
  def const_missing(const)
170
186
  Object.const_get(const)
data/lib/qed/session.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module QED
2
2
 
3
- #require 'qed/config'
3
+ require 'qed/settings'
4
4
  require 'qed/demo'
5
5
 
6
6
  # The Session class encapsulates a set of demonstrations
@@ -9,27 +9,66 @@ module QED
9
9
  #
10
10
  class Session
11
11
 
12
- # Demonstration files.
13
- attr :files
12
+ # Default recognized demos file types.
13
+ DEMO_TYPES = %w{qed rdoc md markdown}
14
+
15
+ #
16
+ CODE_TYPES = %w{rb}
17
+
18
+ # Directory names to omit from automatic selection.
19
+ OMIT_PATHS = %w{applique helpers support sample samples fixture fixtures}
20
+
21
+ # Demonstration files (or globs).
22
+ attr_reader :files
23
+
24
+ # File patterns to omit.
25
+ attr_accessor :omit
14
26
 
15
27
  # Output format.
16
28
  attr_accessor :format
17
29
 
18
- # Trace mode
30
+ # Trace execution?
19
31
  attr_accessor :trace
20
32
 
21
- #
33
+ # Parse mode.
22
34
  attr_accessor :mode
23
35
 
36
+ # Paths to be added to $LOAD_PATH.
37
+ attr_reader :loadpath
38
+
39
+ # Libraries to be required.
40
+ attr_reader :requires
41
+
42
+ # Do not operate from project root?
43
+ attr_accessor :rootless
44
+
45
+ # Selected profile.
46
+ attr_accessor :profile
47
+
48
+ # Returns instance of Settings class.
49
+ attr :settings
50
+
24
51
  # New Session
25
52
  def initialize(files, options={})
26
53
  require_reporters
27
54
 
28
- @files = [files].flatten
55
+ @files = [files].flatten
56
+
57
+ @format = options[:format] || :dotprogress
58
+ @trace = options[:trace] || false
59
+ @mode = options[:mode] || nil
60
+ @profile = options[:profile] || :default
61
+ @loadpath = options[:loadpath] || []
62
+ @requires = options[:requires] || []
29
63
 
30
- @mode = options[:mode]
31
- @trace = options[:trace] || false
32
- @format = options[:format] || :dotprogress
64
+ @omit = OMIT_PATHS # TODO: eventually make configurable
65
+
66
+ @settings = Settings.new(options)
67
+ end
68
+
69
+ #
70
+ def directory
71
+ settings.tmpdir
33
72
  end
34
73
 
35
74
  # Top-level configuration.
@@ -53,8 +92,11 @@ module QED
53
92
  end
54
93
 
55
94
  # Returns an Array of Demo instances.
95
+ #--
96
+ # TODO: Pass settings to demo, so we can get temporary_dirctory.
97
+ #++
56
98
  def demos
57
- @demos ||= files.map{ |file| Demo.new(file, :mode=>mode) }
99
+ @demos ||= demo_files.map{ |file| Demo.new(file, :mode=>mode, :at=>directory) }
58
100
  end
59
101
 
60
102
  #
@@ -63,16 +105,94 @@ module QED
63
105
  end
64
106
 
65
107
  # Run session.
108
+ #--
109
+ # TODO: remove loadpath additions when done
110
+ #++
66
111
  def run
67
- #profile.before_session(self)
68
- reporter.before_session(self)
69
- demos.each do |demo|
70
- demo.run(*observers)
71
- #pid = fork { demo.run(*observers) }
72
- #Process.detach(pid)
112
+ abort "No documents." if demo_files.empty?
113
+
114
+ clear_directory
115
+
116
+ prepare_loadpath
117
+ require_libraries
118
+
119
+ require_profile # TODO: here or in chdir?
120
+
121
+ Dir.chdir(directory) do
122
+ #profile.before_session(self)
123
+ reporter.before_session(self)
124
+ demos.each do |demo|
125
+ demo.run(*observers)
126
+ #pid = fork { demo.run(*observers) }
127
+ #Process.detach(pid)
128
+ end
129
+ reporter.after_session(self)
130
+ #profile.after_session(self)
131
+ end
132
+ end
133
+
134
+ #
135
+ def clear_directory
136
+ settings.clear_directory
137
+ end
138
+
139
+ # Add to load path (from -I option).
140
+ def prepare_loadpath
141
+ loadpath.each{ |dir| $LOAD_PATH.unshift(dir) }
142
+ end
143
+
144
+ # Require libraries (from -r option).
145
+ def require_libraries
146
+ requires.each{ |file| require(file) }
147
+ end
148
+
149
+ #
150
+ def require_profile
151
+ settings.require_profile(profile)
152
+ end
153
+
154
+ # Returns a list of demo files. The files returned depends on the
155
+ # +files+ attribute and if none given, then the current run mode.
156
+ def demo_files
157
+ @demo_files ||= (
158
+ if mode == :comment
159
+ demos_in_comment_mode
160
+ else
161
+ demos_in_normal_mode
162
+ end
163
+ )
164
+ end
165
+
166
+ # Collect default files to process in normal demo mode.
167
+ def demos_in_normal_mode
168
+ demos_gather(DEMO_TYPES)
169
+ end
170
+
171
+ # Collect default files to process in code comment mode.
172
+ #
173
+ # TODO: Sure removing applique files is the best approach here?
174
+ def demos_in_comment_mode
175
+ files = demos_gather(CODE_TYPES)
176
+ files = files.reject{ |f| f.index('applique/') } # don't include applique files ???
177
+ files
178
+ end
179
+
180
+ # Gather list of demo files. Uses +omit+ to remove certain files
181
+ # based on the name of their parent directory.
182
+ def demos_gather(extensions=DEMO_TYPES)
183
+ files = self.files
184
+ #files << default_location if files.empty?
185
+ files = files.map{|pattern| Dir[pattern]}.flatten.uniq
186
+ files = files.map do |file|
187
+ if File.directory?(file)
188
+ Dir[File.join(file,'**','*.{' + extensions.join(',') + '}')]
189
+ else
190
+ file
191
+ end
73
192
  end
74
- reporter.after_session(self)
75
- #profile.after_session(self)
193
+ files = files.flatten.uniq
194
+ files = files.reject{ |f| f =~ Regexp.new('\/'+omit.join('|')+'\/') }
195
+ files.map{|f| File.expand_path(f) }.uniq.sort
76
196
  end
77
197
 
78
198
  # Globally applicable advice.
@@ -82,6 +202,108 @@ module QED
82
202
  # end
83
203
  #end
84
204
 
205
+ #
206
+ def self.cli(*argv)
207
+ require 'optparse'
208
+ require 'shellwords'
209
+
210
+ files, options = cli_parse(argv)
211
+
212
+ if files.empty?
213
+ puts "No files."
214
+ puts options_parser
215
+ exit -1
216
+ end
217
+
218
+ session = new(files, options)
219
+ session.run
220
+ end
221
+
222
+ # Instance of OptionParser
223
+ def self.cli_parse(argv)
224
+ options = {}
225
+ options_parser = OptionParser.new do |opt|
226
+ opt.banner = "Usage: qed [options] <files...>"
227
+
228
+ opt.separator("Custom Profiles:") unless settings.profiles.empty?
229
+
230
+ settings.profiles.each do |name, value|
231
+ o = "--#{name}"
232
+ opt.on(o, "#{name} custom profile") do
233
+ options[:profile] = name.to_sym
234
+ end
235
+ end
236
+
237
+ opt.separator("Report Formats (pick one):")
238
+ opt.on('--dotprogress', '-d', "use dot-progress reporter [default]") do
239
+ options[:format] = :dotprogress
240
+ end
241
+ opt.on('--verbatim', '-v', "use verbatim reporter") do
242
+ options[:format] = :verbatim
243
+ end
244
+ opt.on('--bullet', '-b', "use bullet-point reporter") do
245
+ options[:format] = :bullet
246
+ end
247
+ opt.on('--html', '-h', "use underlying HTML reporter") do
248
+ options[:format] = :html
249
+ end
250
+ #opt.on('--script', "psuedo-reporter") do
251
+ # options[:format] = :script # psuedo-reporter
252
+ #end
253
+ opt.on('--format', '-f FORMAT', "use custom reporter") do |format|
254
+ options[:format] = format.to_sym
255
+ end
256
+
257
+ opt.separator("Control Options:")
258
+ opt.on('--comment', '-c', "run comment code") do
259
+ options[:mode] = :comment
260
+ end
261
+ opt.on('--profile', '-p NAME', "load runtime profile") do |name|
262
+ options[:profile] = name
263
+ end
264
+ opt.on('--loadpath', "-I PATH", "add paths to $LOAD_PATH") do |paths|
265
+ options[:loadpath] = paths.split(/[:;]/).map{|d| File.expand_path(d)}
266
+ end
267
+ opt.on('--require', "-r LIB", "require library") do |paths|
268
+ options[:requires] = paths.split(/[:;]/)
269
+ end
270
+ opt.on('--rootless', '-R', "run from system-wide temporary directory") do
271
+ options[:rootless] = true
272
+ end
273
+ opt.on('--trace', '-t', "show full backtraces for exceptions") do
274
+ options[:trace] = true
275
+ end
276
+ opt.on('--warn', "run with warnings turned on") do
277
+ $VERBOSE = true # wish this were called $WARN!
278
+ end
279
+ opt.on('--debug', "exit immediately upon raised exception") do
280
+ $DEBUG = true
281
+ end
282
+
283
+ opt.separator("Optional Commands:")
284
+ opt.on_tail('--version', "display version") do
285
+ puts "QED #{QED::VERSION}"
286
+ exit
287
+ end
288
+ opt.on_tail('--copyright', "display copyrights") do
289
+ puts "Copyright (c) 2008 Thomas Sawyer, Apache 2.0 License"
290
+ exit
291
+ end
292
+ opt.on_tail('--help', '-h', "display this help message") do
293
+ puts opt
294
+ exit
295
+ end
296
+ end
297
+ options_parser.parse!(argv)
298
+ return argv, options
299
+ end
300
+
301
+ # TODO: Pass to Session class, instead of acting global.
302
+ # It is used at the class level to get profiles for the cli.
303
+ def self.settings
304
+ @settings ||= Settings.new
305
+ end
306
+
85
307
  end#class Session
86
308
 
87
309
  end#module QED