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
@@ -1,21 +1,33 @@
1
1
  module QED
2
2
 
3
- # Ecapsulate confiduration information needed for QED
3
+ # Ecapsulate configuration information needed for QED to
4
4
  # run and set user and project options.
5
+ #
6
+ # Configuration for QED is placed in a `.config.rb` or `config.rb` file.
7
+ # In this file special configuration setups can be placed to automatically
8
+ # effect QED execution, in particular optional profiles can be defined.
9
+ #
10
+ # qed do
11
+ # profile :coverage do
12
+ # require 'simplecov'
13
+ # SimpleCov.start do
14
+ # coverage_dir 'log/coverage'
15
+ # add_group "Shared" do |src_file|
16
+ # /lib\/dotruby\/v(\d+)(.*?)$/ !~ src_file.filename
17
+ # end
18
+ # add_group "Revision 0", "lib/dotruby/v0"
19
+ # end
20
+ # end
21
+ # end
22
+ #
5
23
  class Settings
6
24
 
7
25
  require 'tmpdir'
8
26
  require 'fileutils'
9
-
10
- # Configuration directory `.qed`, `.config/qed` or `config/qed`.
11
- # In this directory special configuration files can be placed
12
- # to autmatically effect qed execution. In particular you can
13
- # add a `profiles.yml` file to setup convenient execution
14
- # scenarios.
15
- CONFIG_PATTERN = "{.,.set/,set/.config/,config/}qed"
27
+ require 'confection'
16
28
 
17
29
  # Glob pattern used to search for project's root directory.
18
- ROOT_PATTERN = '{.ruby,.git/,.hg/,_darcs/,.qed/,.set/qed/,set/qed/.config/qed/,config/qed/}'
30
+ ROOT_PATTERN = '{.confile,.confile.rb,confile,confile.rb,.ruby,.git/,.hg/,_darcs/}'
19
31
 
20
32
  # Home directory.
21
33
  HOME = File.expand_path('~')
@@ -23,29 +35,39 @@ module QED
23
35
  #
24
36
  def initialize(options={})
25
37
  @rootless = options[:rootless]
38
+ @profiles = {}
39
+
40
+ @root = @rootless ? system_tmpdir : find_root
41
+
42
+ # Set global. TODO: find away to not need this ?
43
+ $ROOT = @root
44
+
45
+ confection('qed').exec
26
46
  end
27
47
 
48
+ # Operate relative to project root directory, or use system's location.
28
49
  #
29
50
  def rootless?
30
51
  @rootless
31
52
  end
32
53
 
33
54
  # Project's root directory.
55
+ #
34
56
  def root_directory
35
- @root_directory ||= find_root
57
+ @root
36
58
  end
37
59
 
38
- # Project's QED configuration directory.
39
- # TODO: rename to `config_directory` ?
40
- def settings_directory
41
- @settings_directory ||= find_settings
42
- end
60
+ # Shorthand for `#root_directory`.
61
+ alias_method :root, :root_directory
43
62
 
63
+ # Temporary directory. If `#rootless?` return true then this will be
64
+ # a system's temporary directory (e.g. `/tmp/qed/foo/20111117242323/`).
65
+ # Otherwise, it will local to the project's root int `tmp/qed/`.
44
66
  #
45
67
  def temporary_directory
46
68
  @temporary_directory ||= (
47
69
  if rootless?
48
- File.join(Dir.tmpdir, 'qed', File.filename(Dir.pwd), Time.new.strftime("%Y%m%d%H%M%S"))
70
+ system_tmpdir
49
71
  else
50
72
  File.join(root_directory, 'tmp', 'qed')
51
73
  end
@@ -53,7 +75,7 @@ module QED
53
75
  )
54
76
  end
55
77
 
56
- #
78
+ # Shorthand for `#temporary_directory`.
57
79
  alias_method :tmpdir, :temporary_directory
58
80
 
59
81
  # Remove and recreate temporary working directory.
@@ -62,39 +84,61 @@ module QED
62
84
  FileUtils.mkdir_p(tmpdir)
63
85
  end
64
86
 
65
- # Profile configurations.
66
- def profiles
67
- @profiles ||= (
68
- files = Dir["#{settings_directory}/*.rb"]
69
- files.map do |file|
70
- File.basename(file).chomp('.rb')
71
- end
72
- )
87
+ # Define a profile.
88
+ #
89
+ # @param [#to_s] name
90
+ # Name of profile.
91
+ #
92
+ # @yield Procedure to run for profile.
93
+ #
94
+ # @return [Proc] The procedure.
95
+ def profile(name, &block)
96
+ @profiles[name.to_s] = block
73
97
  end
74
98
 
75
- # Require requirement file (from -e option).
76
- def require_profile(profile)
77
- return unless settings_directory
78
- if file = Dir["#{settings_directory}/#{profile}.rb"].first
79
- require(file)
99
+ # Keeps a list of defined profiles.
100
+ attr_accessor :profiles
101
+
102
+ # Profile configurations.
103
+ #def profiles
104
+ # @profiles ||= (
105
+ # files = Dir["#{settings_directory}/*.rb"]
106
+ # files.map do |file|
107
+ # File.basename(file).chomp('.rb')
108
+ # end
109
+ # )
110
+ #end
111
+
112
+ # Load QED profile (from -e option).
113
+ def load_profile(name)
114
+ if profile = profiles[name.to_s]
115
+ instance_eval(&profile)
116
+ #eval('self', TOPLEVEL_BINDING).instance_eval(&prof)
80
117
  end
118
+ #return unless settings_directory
119
+ #if file = Dir["#{settings_directory}/#{profile}.rb"].first
120
+ # require(file)
121
+ #end
81
122
  end
82
123
 
83
124
  # Locate project's root directory. This is done by searching upward
84
- # in the file heirarchy for the existence of one of the following
85
- # path names, each group being tried in turn.
125
+ # in the file heirarchy for the existence of one of the following:
86
126
  #
87
- # * .git/
88
- # * .hg/
89
- # * _darcs/
90
- # * .config/qed/
91
- # * config/qed/
92
- # * .qed/
93
- # * .ruby
127
+ # .confile
128
+ # confile
129
+ # .confile.rb
130
+ # confile.rb
131
+ # .ruby
132
+ # .git/
133
+ # .hg/
134
+ # _darcs/
94
135
  #
95
136
  # Failing to find any of these locations, resort to the fallback:
96
137
  #
97
- # * lib/
138
+ # lib/
139
+ #
140
+ # If that isn't found, then returns a temporary system location.
141
+ # and sets `rootless` to true.
98
142
  #
99
143
  def find_root(path=nil)
100
144
  path = File.expand_path(path || Dir.pwd)
@@ -103,45 +147,31 @@ module QED
103
147
  root = lookup(ROOT_PATTERN, path)
104
148
  return root if root
105
149
 
106
- #root = lookup(path, '{.qed,.config/qed,config/qed}/')
107
- #return root if root
108
-
109
- #root = lookup(path, '{qed,demo,demos}/')
150
+ #root = lookup(path, '{qed,demo,spec}/')
110
151
  #return root if root
111
152
 
112
153
  root = lookup('lib/', path)
113
154
 
114
- return root if root
155
+ if !root
156
+ warn "QED is running rootless."
157
+ root = system_tmpdir
158
+ @rootless = true
159
+ end
115
160
 
116
- abort "QED failed to resolve project's root location.\n" +
117
- "QED looks for following entries to identify the root:\n" +
118
- " .set/qed/\n" +
119
- " set/qed/\n" +
120
- " .config/qed/\n" +
121
- " config/qed/\n" +
122
- " .qed/\n" +
123
- " .ruby\n" +
124
- " lib/\n" +
125
- "Please add one of them to your project to proceed."
126
- end
161
+ return root
127
162
 
128
- # Locate configuration directory by seaching up the
129
- # file hierachy relative to the working directory
130
- # for one of the following paths:
131
- #
132
- # * .config/qed/
133
- # * config/qed/
134
- # * .qed/
135
- #
136
- def find_settings
137
- Dir[File.join(root_directory,CONFIG_PATTERN)].first
163
+ #abort "QED failed to resolve project's root location.\n" +
164
+ # "QED looks for following entries to identify the root:\n" +
165
+ # ROOT_PATTERN +
166
+ # "Please add one of them to your project to proceed."
138
167
  end
139
168
 
169
+ # TODO: Use Dir.ascend from Ruby Facets.
170
+
140
171
  # Lookup path +glob+, searching each higher directory
141
172
  # in turn until just before the users home directory
142
173
  # is reached or just before the system's root directory.
143
174
  #
144
- # TODO: include HOME directory in search?
145
175
  def lookup(glob, path=Dir.pwd)
146
176
  until path == HOME or path == '/' # until home or root
147
177
  mark = Dir.glob(File.join(path,glob), File::FNM_CASEFOLD).first
@@ -150,6 +180,13 @@ module QED
150
180
  end
151
181
  end
152
182
 
183
+ #
184
+ def system_tmpdir
185
+ @system_tmpdir ||= (
186
+ File.join(Dir.tmpdir, 'qed', File.basename(Dir.pwd), Time.new.strftime("%Y%m%d%H%M%S"))
187
+ )
188
+ end
189
+
153
190
  end
154
191
 
155
192
  end
@@ -0,0 +1,237 @@
1
+ module QED
2
+
3
+ # A Step consists of a flush region of text and the indented
4
+ # text the follows it. QED breaks all demos down into step
5
+ # for evaluation.
6
+ #
7
+ # Steps form a doubly linkes list, each having access to the step
8
+ # before and the step after them. Potentially this could be used
9
+ # by very advnaced matchers, to vary executation by earlier or later
10
+ # content of a demo.
11
+ #
12
+ class Step
13
+
14
+ # Ths demo to which the step belongs.
15
+ # @return [Demo] demo
16
+ attr :demo
17
+
18
+ # Block lines code/text.
19
+ #attr :lines
20
+
21
+ # Previous step.
22
+ # @return [Step] previous step
23
+ attr :back_step
24
+
25
+ # Next step.
26
+ # @return [Step] next step
27
+ attr :next_step
28
+
29
+ # Step up a new step.
30
+ #
31
+ # @param [Demo] demo
32
+ # The demo to which the step belongs.
33
+ #
34
+ # @param [Array<Array<Integer,String>]] explain_lines
35
+ # The step's explaination text, broken down into an array
36
+ # of `[line number, line text]` entries.
37
+ #
38
+ # @param [Array<Array<Integer,String>]] example_lines
39
+ # The steps example text, broken down into an array
40
+ # of `[line number, line text]` entries.
41
+ #
42
+ # @param [Step] last
43
+ # The previous step in the demo.
44
+ #
45
+ def initialize(demo, explain_lines, example_lines, last)
46
+ #QED.all_steps << self
47
+
48
+ @demo = demo
49
+ @file = demo.file
50
+
51
+ #@lines = []
52
+
53
+ @explain_lines = explain_lines
54
+ @example_lines = example_lines
55
+
56
+ @back_step = last
57
+ @back_step.next_step = self if @back_step
58
+ end
59
+
60
+ # The step's explaination text, broken down into an array
61
+ # of `[line number, line text]` entries.
62
+ #
63
+ # @return [Array<Array<Integer,String>]] explain_lines
64
+ attr :explain_lines
65
+
66
+ # The steps example text, broken down into an array
67
+ # of `[line number, line text]` entries.
68
+ #
69
+ # @return [Array<Array<Integer,String>]] example_lines
70
+ attr :example_lines
71
+
72
+ # Ths file to which the step belongs.
73
+ #
74
+ # @return [String] file path
75
+ def file
76
+ demo.file
77
+ end
78
+
79
+ # Full text of block including both explination and example text.
80
+ def to_s
81
+ (@explain_lines + @example_lines).map{ |lineno, line| line }.join("")
82
+ end
83
+
84
+ # Description text.
85
+ def explain
86
+ @explain ||= @explain_lines.map{ |lineno, line| line }.join("")
87
+ end
88
+
89
+ # Alternate term for #explain.
90
+ alias_method :description, :explain
91
+
92
+ # @deprecated
93
+ alias_method :text, :explain
94
+
95
+ # TODO: Support embedded rule steps ?
96
+
97
+ #
98
+ #def rule?
99
+ # @is_rule ||= (/\A(given|when|rule|before|after)[:.]/i.match(text))
100
+ #end
101
+
102
+ #
103
+ #def rule_text
104
+ # rule?.post_match.strip
105
+ #end
106
+
107
+ # TODO: better name than :proc ?
108
+
109
+ #
110
+ def type
111
+ assertive? ? :test : :proc
112
+ end
113
+
114
+ # A step is a heading if it's description starts with a '=' or '#'.
115
+ def heading?
116
+ @is_heading ||= (/\A[=#]/ =~ explain)
117
+ end
118
+
119
+ # Any commentary ending in `:` will mark the example
120
+ # text as a plain text *sample* and not code to be evaluated.
121
+ def data?
122
+ @is_data ||= explain.strip.end_with?(':')
123
+ end
124
+
125
+ # Is the example text code to be evaluated?
126
+ def code?
127
+ !data? && example?
128
+ end
129
+
130
+ # First line of example text.
131
+ def lineno
132
+ @lineno ||= (
133
+ if @example_lines.first
134
+ @example_lines.first.first
135
+ elsif @explain_lines.first
136
+ @explain_lines.first.first
137
+ else
138
+ 1
139
+ end
140
+ )
141
+ end
142
+
143
+ def explain_lineno
144
+ @explain_lines.first ? @explain_lines.first.first : 1
145
+ end
146
+
147
+ def example_lineno
148
+ @example_lines.first ? @example_lines.first.first : 1
149
+ end
150
+
151
+ # Does the block have an example?
152
+ def example?
153
+ ! example.strip.empty?
154
+ end
155
+ alias has_example? example?
156
+
157
+ #
158
+ def example
159
+ @example ||= (
160
+ if data?
161
+ @example_lines.map{ |lineno, line| line }.join("")
162
+ else
163
+ tweak_code
164
+ end
165
+ )
166
+ end
167
+ alias_method :code, :example
168
+ alias_method :data, :example
169
+
170
+ # Returns any extra arguments the step passes along to rules.
171
+ def arguments
172
+ []
173
+ end
174
+
175
+ # Clean up the example text, removing unccesseary white lines
176
+ # and triple quote brackets, but keep indention intact.
177
+ #
178
+ def clean_example
179
+ str = example.chomp.sub(/\A\n/,'')
180
+ if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str)
181
+ str = md[1]
182
+ end
183
+ str.rstrip
184
+ end
185
+
186
+ # TODO: We need to preserve the indentation for the verbatim reporter.
187
+ #def clean_quote(text)
188
+ # text = text.tabto(0).chomp.sub(/\A\n/,'')
189
+ # if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text)
190
+ # text = md[1]
191
+ # end
192
+ # text.rstrip
193
+ #end
194
+
195
+ # When the text is sample text and passed to an adivce block, this
196
+ # provides the prepared form of the example text, removing white lines,
197
+ # triple quote brackets and indention.
198
+ #
199
+ def sample_text
200
+ str = example.tabto(0).chomp.sub(/\A\n/,'')
201
+ if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str)
202
+ str = md[1]
203
+ end
204
+ str.rstrip
205
+ end
206
+
207
+ # TODO: object_hexid
208
+ def inspect
209
+ str = text[0,24].gsub("\n"," ")
210
+ %[\#<Step:#{object_id} "#{str} ...">]
211
+ end
212
+
213
+ #
214
+ def assertive?
215
+ @assertive ||= !text.strip.end_with?('^')
216
+ end
217
+
218
+ protected
219
+
220
+ #
221
+ def next_step=(n)
222
+ @next_step = n
223
+ end
224
+
225
+ private
226
+
227
+ #
228
+ def tweak_code
229
+ code = @example_lines.map{ |lineno, line| line }.join("")
230
+ code.gsub!(/\n\s*\#\ ?\=\>/, '.assert = ')
231
+ code.gsub!(/\s*\#\ ?\=\>/, '.assert = ')
232
+ code
233
+ end
234
+
235
+ end
236
+
237
+ end