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.
- data/.ruby +4 -3
- data/.yardopts +3 -0
- data/HISTORY.rdoc +71 -35
- data/README.rdoc +9 -10
- data/bin/qed +1 -1
- data/bin/qedoc +2 -1
- data/lib/qed.rb +2 -5
- data/lib/qed.yml +4 -3
- data/lib/qed/applique.rb +57 -24
- data/lib/qed/cli.rb +8 -0
- data/lib/qed/cli/qed.rb +124 -0
- data/lib/qed/demo.rb +35 -39
- data/lib/qed/document.rb +5 -3
- data/lib/qed/document/template.rhtml +1 -0
- data/lib/qed/evaluator.rb +227 -199
- data/lib/qed/parser.rb +60 -282
- data/lib/qed/reporter/abstract.rb +54 -58
- data/lib/qed/reporter/dotprogress.rb +6 -4
- data/lib/qed/reporter/html.rb +112 -31
- data/lib/qed/reporter/tapy.rb +95 -125
- data/lib/qed/reporter/verbatim.rb +80 -38
- data/lib/qed/scope.rb +35 -48
- data/lib/qed/session.rb +35 -140
- data/lib/qed/settings.rb +104 -67
- data/lib/qed/step.rb +237 -0
- data/{spec → qed}/01_demos.rdoc +0 -0
- data/{spec → qed}/02_advice.rdoc +18 -7
- data/qed/03_helpers.rdoc +44 -0
- data/{spec → qed}/04_samples.rdoc +4 -4
- data/{spec → qed}/05_quote.rdoc +3 -3
- data/{spec → qed}/07_toplevel.rdoc +0 -0
- data/{spec → qed}/08_cross_script.rdoc +0 -0
- data/{spec → qed}/09_cross_script.rdoc +0 -0
- data/{spec → qed}/10_constant_lookup.rdoc +2 -2
- data/qed/11_embedded_rules.rdoc +46 -0
- data/{test/integration/topcode.rdoc → qed/99_issues/02_topcode.rdoc} +0 -0
- data/{spec → qed}/applique/constant.rb +0 -0
- data/{spec → qed}/applique/env.rb +0 -0
- data/{spec → qed}/applique/fileutils.rb +0 -0
- data/{spec → qed}/applique/markup.rb +0 -0
- data/{spec → qed}/applique/toplevel.rb +0 -0
- data/{spec → qed}/helpers/advice.rb +6 -7
- data/{spec → qed}/helpers/toplevel.rb +0 -0
- data/{spec → qed}/samples/data.txt +0 -0
- data/{spec → qed}/samples/table.yml +0 -0
- metadata +44 -39
- data/LICENSE.rdoc +0 -31
- data/SPECSHEET.rdoc +0 -456
- data/lib/qed/advice.rb +0 -158
- data/lib/qed/reporter/bullet.rb +0 -91
- data/lib/qed/reporter/dtrace.rb +0 -67
- data/lib/yard-qed.rb +0 -1
- data/spec/03_helpers.rdoc +0 -43
- data/spec/applique/quote.rb +0 -4
- data/spec/helpers/sample.rb +0 -4
data/lib/qed/settings.rb
CHANGED
@@ -1,21 +1,33 @@
|
|
1
1
|
module QED
|
2
2
|
|
3
|
-
# Ecapsulate
|
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
|
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
|
-
@
|
57
|
+
@root
|
36
58
|
end
|
37
59
|
|
38
|
-
#
|
39
|
-
|
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
|
-
|
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
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
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
|
-
#
|
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, '{
|
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
|
-
|
155
|
+
if !root
|
156
|
+
warn "QED is running rootless."
|
157
|
+
root = system_tmpdir
|
158
|
+
@rootless = true
|
159
|
+
end
|
115
160
|
|
116
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
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
|
data/lib/qed/step.rb
ADDED
@@ -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
|