qed 2.6.3 → 2.7.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.
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