lucid 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -3
  3. data/HISTORY.md +12 -0
  4. data/LICENSE +0 -3
  5. data/README.md +7 -5
  6. data/Rakefile +0 -14
  7. data/lib/lucid.rb +4 -0
  8. data/lib/lucid/cli/app.rb +1 -5
  9. data/lib/lucid/cli/context.rb +8 -6
  10. data/lib/lucid/cli/profile.rb +9 -9
  11. data/lib/lucid/context.rb +1 -1
  12. data/lib/lucid/interface_rb/matcher.rb +1 -2
  13. data/lib/lucid/platform.rb +2 -1
  14. data/lucid.gemspec +12 -12
  15. data/spec/lucid/lucid_spec.rb +7 -0
  16. data/spec/spec_helper.rb +0 -19
  17. metadata +34 -116
  18. data/.travis.yml +0 -15
  19. data/lib/lucid/sequence.rb +0 -5
  20. data/lib/lucid/sequence/sequence_errors.rb +0 -64
  21. data/lib/lucid/sequence/sequence_group.rb +0 -35
  22. data/lib/lucid/sequence/sequence_phrase.rb +0 -166
  23. data/lib/lucid/sequence/sequence_steps.rb +0 -20
  24. data/lib/lucid/sequence/sequence_support.rb +0 -26
  25. data/lib/lucid/sequence/sequence_template.rb +0 -354
  26. data/spec/lucid/ansicolor_spec.rb +0 -31
  27. data/spec/lucid/app_spec.rb +0 -82
  28. data/spec/lucid/ast/background_spec.rb +0 -128
  29. data/spec/lucid/ast/doc_string_spec.rb +0 -36
  30. data/spec/lucid/ast/feature_spec.rb +0 -66
  31. data/spec/lucid/ast/outline_table_spec.rb +0 -21
  32. data/spec/lucid/ast/scenario_outline_spec.rb +0 -81
  33. data/spec/lucid/ast/specs_spec.rb +0 -48
  34. data/spec/lucid/ast/step_invocation_spec.rb +0 -45
  35. data/spec/lucid/ast/step_spec.rb +0 -72
  36. data/spec/lucid/ast/table_spec.rb +0 -265
  37. data/spec/lucid/ast/tdl_factory.rb +0 -78
  38. data/spec/lucid/ast/tdl_walker_spec.rb +0 -21
  39. data/spec/lucid/context_spec.rb +0 -328
  40. data/spec/lucid/duration_spec.rb +0 -22
  41. data/spec/lucid/facade_spec.rb +0 -31
  42. data/spec/lucid/factory_spec.rb +0 -16
  43. data/spec/lucid/matcher_spec.rb +0 -127
  44. data/spec/lucid/options_spec.rb +0 -346
  45. data/spec/lucid/orchestrator_spec.rb +0 -117
  46. data/spec/lucid/pending_spec.rb +0 -45
  47. data/spec/lucid/progress_spec.rb +0 -34
  48. data/spec/lucid/rb_step_definition_spec.rb +0 -127
  49. data/spec/lucid/rb_transform_spec.rb +0 -24
  50. data/spec/lucid/regexp_argument_matcher_spec.rb +0 -19
  51. data/spec/lucid/results_spec.rb +0 -81
  52. data/spec/lucid/runtime_spec.rb +0 -38
  53. data/spec/lucid/sequences/sequence_conditional_spec.rb +0 -74
  54. data/spec/lucid/sequences/sequence_group_spec.rb +0 -55
  55. data/spec/lucid/sequences/sequence_phrase_spec.rb +0 -122
  56. data/spec/lucid/sequences/sequence_placeholder_spec.rb +0 -56
  57. data/spec/lucid/sequences/sequence_section_spec.rb +0 -61
  58. data/spec/lucid/sequences/sequence_support_spec.rb +0 -65
  59. data/spec/lucid/sequences/sequence_template_spec.rb +0 -298
  60. data/spec/lucid/step_match_spec.rb +0 -55
@@ -1,15 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 1.9.3
5
- - 2.0.0
6
- - 2.1.0
7
-
8
- branches:
9
- only:
10
- - master
11
- - develop
12
-
13
- notifications:
14
- recipients:
15
- - jeffnyman@gmail.com
@@ -1,5 +0,0 @@
1
- # This file is the starting point for the Sequence functionality.
2
- # It will only reference files in the sequence directory and does
3
- # not impact the core runtime at all.
4
-
5
- require 'lucid/sequence/sequence_support'
@@ -1,64 +0,0 @@
1
- module Sequence
2
-
3
- class SequenceError < StandardError
4
- end
5
-
6
- class DuplicateSequenceError < SequenceError
7
- def initialize(phrase)
8
- super("A sequence with phrase '#{phrase}' already exists.")
9
- end
10
- end
11
-
12
- class UnknownSequenceError < SequenceError
13
- def initialize(phrase)
14
- super("Unknown sequence step with phrase: '#{phrase}'.")
15
- end
16
- end
17
-
18
- class EmptyParameterError < SequenceError
19
- def initialize(text)
20
- super("An empty or blank parameter occurred in '#{text}'.")
21
- end
22
- end
23
-
24
- class InvalidElementError < SequenceError
25
- def initialize(tag, invalid_element)
26
- msg = "The invalid element '#{invalid_element}' occurs in the parameter '#{tag}'."
27
- super(msg)
28
- end
29
- end
30
-
31
- class UselessPhraseParameter < SequenceError
32
- def initialize(param)
33
- super("The phrase parameter '#{param}' does not appear in any step.")
34
- end
35
- end
36
-
37
- class DataTableNotFound < SequenceError
38
- def initialize(phrase)
39
- msg = "The step with phrase [#{phrase}]: requires a data table."
40
- super(msg)
41
- end
42
- end
43
-
44
- class UnknownParameterError < SequenceError
45
- def initialize(name)
46
- super("Unknown sequence step parameter '#{name}'.")
47
- end
48
- end
49
-
50
- class AmbiguousParameterValue < SequenceError
51
- def initialize(name, phrase, table)
52
- msg = "The sequence parameter '#{name}' has value '#{phrase}' and '#{table}'."
53
- super(msg)
54
- end
55
- end
56
-
57
- class UnreachableStepParameter < SequenceError
58
- def initialize(param)
59
- msg = "The step parameter '#{param}' does not appear in the phrase."
60
- super(msg)
61
- end
62
- end
63
-
64
- end
@@ -1,35 +0,0 @@
1
- require 'singleton'
2
- require 'lucid/sequence/sequence_phrase'
3
-
4
- module Sequence
5
- class SequenceGroup
6
- include Singleton
7
-
8
- def add_sequence(phrase, sequence, data)
9
- new_sequence = SequencePhrase.new(phrase, sequence, data)
10
- raise DuplicateSequenceError.new(phrase) if find_sequence(phrase, data)
11
- sequence_steps[new_sequence.key] = new_sequence
12
- end
13
-
14
- def sequence_steps
15
- @sequence_steps ||= {}
16
- return @sequence_steps
17
- end
18
-
19
- def generate_steps(phrase, data = nil)
20
- data_table =! data.nil?
21
- sequence = find_sequence(phrase, data_table)
22
- raise UnknownSequenceError.new(phrase) if sequence.nil?
23
- return sequence.expand(phrase, data)
24
- end
25
-
26
- def clear
27
- sequence_steps.clear
28
- end
29
-
30
- def find_sequence(phrase, data)
31
- key = SequencePhrase.sequence_key(phrase, data, :invoke)
32
- return sequence_steps[key]
33
- end
34
- end
35
- end
@@ -1,166 +0,0 @@
1
- require 'lucid/sequence/sequence_template'
2
- require 'lucid/sequence/sequence_errors'
3
-
4
- module Sequence
5
- class SequencePhrase
6
-
7
- attr_reader :key
8
- attr_reader :values
9
- attr_reader :template
10
- attr_reader :phrase_params
11
-
12
- ParameterConstant = { 'quotes' => '"""'}
13
-
14
- def initialize(phrase, sequence, data)
15
- @key = self.class.sequence_key(phrase, data, :define)
16
- #puts "*** [Sequence Step] - Key: #{@key}"
17
-
18
- @phrase_params = scan_parameters(phrase, :define)
19
- #puts "*** [Sequence Step] - Phrase Params: #{@phrase_params}"
20
-
21
- transformed_steps = preprocess(sequence)
22
- #puts "*** [Sequence Step] - Steps: \n#{transformed_steps}"
23
-
24
- @template = SequenceTemplate::Engine.new(transformed_steps)
25
- #puts "\n*** [Sequence Step] - Template: #{@template.inspect}\n"
26
-
27
- phrase_variables = template.variables
28
- #puts "\n*** [Sequence Step] - Phrase Variables: #{phrase_variables}"
29
-
30
- @values = validate_phrase_values(@phrase_params, phrase_variables)
31
- @values.concat(phrase_variables)
32
- #puts "*** [Sequence Step] - Values: #{@values}"
33
-
34
- @values.uniq!
35
- end
36
-
37
- def self.sequence_key(phrase, data, mode)
38
- new_phrase = phrase.strip
39
-
40
- # These lines replace every series of whitespace with an underscore.
41
- # For that to work, I have to make sure there are no existing
42
- # underscore characters in the first place so any pre-existing
43
- # underscores get removed first.
44
- new_phrase.gsub!(/_/, '')
45
- new_phrase.gsub!(/\s+/, '_')
46
-
47
- pattern = case mode
48
- when :define
49
- /<(?:[^\\<>]|\\.)*>/
50
- when :invoke
51
- /"([^\\"]|\\.)*"/
52
- end
53
-
54
- # Here 'patterned' means that for a given phrase, any bit of text
55
- # between quotes or chevrons is replaced by the letter X.
56
- patterned = new_phrase.gsub(pattern, 'X')
57
-
58
- key = patterned + (data ? '_T' : '')
59
-
60
- return key
61
- end
62
-
63
- # "Scanning parameters" means that a phrase will be scanned to find any
64
- # text between chevrons or double quotes. These will be placed into an
65
- # array.
66
- def scan_parameters(phrase, mode)
67
- pattern = case mode
68
- when :define
69
- /<((?:[^\\<>]|\\.)*)>/
70
- when :invoke
71
- /"((?:[^\\"]|\\.)*)"/
72
- end
73
-
74
- result = phrase.scan(pattern)
75
- params = result.flatten.compact
76
-
77
- # Any escaped double quotes need to be replaced by a double quote.
78
- params.map! { |item| item.sub(/\\"/, '"') } if mode == :invoke
79
-
80
- return params
81
- end
82
-
83
- def preprocess(sequence)
84
- # Split text into individual lines and make sure to remove any lines
85
- # with hash style comments.
86
- lines = sequence.split(/\r\n?|\n/)
87
- processed = lines.reject { |line| line =~ /\s*#/ }
88
-
89
- return processed.join("\n")
90
- end
91
-
92
- # The goal of this method is to check for various inconsistencies that
93
- # can occur between parameter names in the sequence phrase and those
94
- # in the actual steps.
95
- def validate_phrase_values(params, phrase_variables)
96
- # This covers the case when phrase names a parameter that never
97
- # occurs in any of the steps.
98
- params.each do |param|
99
- unless phrase_variables.include? param
100
- raise UselessPhraseParameter.new(param)
101
- end
102
- end
103
-
104
- # This covers the case when a step has a parameter that never appears
105
- # in the sequence phrase and when data table is not being used.
106
- unless data_table_required?
107
- phrase_variables.each do |variable|
108
- unless params.include?(variable) || ParameterConstant.include?(variable)
109
- raise UnreachableStepParameter.new(variable)
110
- end
111
- end
112
- end
113
-
114
- return phrase_params.dup
115
- end
116
-
117
- def data_table_required?
118
- return key =~ /_T$/
119
- end
120
-
121
- def expand(phrase, data)
122
- params = validate_params(phrase, data)
123
- params = ParameterConstant.merge(params)
124
- return template.output(nil, params)
125
- end
126
-
127
- def validate_params(phrase, data)
128
- sequence_parameters = {}
129
-
130
- quoted_values = scan_parameters(phrase, :invoke)
131
- quoted_values.each_with_index do |val, index|
132
- sequence_parameters[phrase_params[index]] = val
133
- end
134
-
135
- unless data.nil?
136
- data.each do |row|
137
- (key, value) = validate_row(row, sequence_parameters)
138
- if sequence_parameters.include? key
139
- if sequence_parameters[key].kind_of?(Array)
140
- sequence_parameters[key] << value
141
- else
142
- sequence_parameters[key] = [sequence_parameters[key], value]
143
- end
144
- else
145
- sequence_parameters[key] = value
146
- end
147
- end
148
- end
149
-
150
- return sequence_parameters
151
- end
152
-
153
- def validate_row(row, params)
154
- (key, value) = row
155
-
156
- raise UnknownParameterError.new(key) unless values.include? key
157
-
158
- if (phrase_params.include? key) && (params[key] != value)
159
- raise AmbiguousParameterValue.new(key, params[key], value)
160
- end
161
-
162
- return row
163
- end
164
-
165
- end
166
- end
@@ -1,20 +0,0 @@
1
- # The only test definitions that should go in here are those that
2
- # will build sequence steps. Meaning, each test step here is meant
3
- # to be one that is equivalent to a sequence of test steps.
4
-
5
- Given(/^the step "(?:Given|When|Then|\*) \[((?:[^\\\]]|\\.)+)\](:?)" is defined to mean:$/) do |phrase, table, sequence|
6
- data_provided = (table == ':')
7
- add_sequence(phrase, sequence, data_provided)
8
- end
9
-
10
- When(/^\[((?:[^\\\]]|\\.)+)\]$/) do |phrase|
11
- invoke_sequence(phrase)
12
- end
13
-
14
- When(/^\[([^\]]+)\]:$/) do |phrase, data_table|
15
- unless data_table.kind_of?(Lucid::AST::Table)
16
- raise Sequence::DataTableNotFound.new(phrase)
17
- end
18
-
19
- invoke_sequence(phrase, data_table.raw)
20
- end
@@ -1,26 +0,0 @@
1
- require 'lucid/sequence/sequence_group'
2
- require 'lucid/sequence/sequence_errors'
3
-
4
- module Sequence
5
- module SequenceSupport
6
-
7
- def add_sequence(phrase, sequence, data)
8
- SequenceGroup.instance.add_sequence(phrase, sequence, data)
9
- end
10
-
11
- def invoke_sequence(phrase, data = nil)
12
- # It's necessary to generate textual versions of all the steps that
13
- # are to be executed.
14
- group = SequenceGroup.instance
15
- generated_steps = group.generate_steps(phrase, data)
16
-
17
- # This statement causes Lucid to execute the generated test steps.
18
- steps(generated_steps)
19
- end
20
-
21
- def clear_sequences
22
- SequenceGroup.instance.clear
23
- end
24
-
25
- end
26
- end
@@ -1,354 +0,0 @@
1
- require 'strscan'
2
-
3
- module Sequence
4
- module SequenceTemplate
5
-
6
- class StaticText
7
- attr_reader :source
8
-
9
- def initialize(source)
10
- @source = source
11
- end
12
-
13
- def output(context, params)
14
- return source
15
- end
16
- end
17
-
18
- class UnaryElement
19
- attr_reader :name
20
-
21
- def initialize(name)
22
- @name = name
23
- end
24
-
25
- def retrieve_value_from(context, params)
26
- actual_value = params[name]
27
-
28
- if actual_value.nil? && context.respond_to?(name.to_sym)
29
- actual_value = context.send(name.to_sym)
30
- end
31
-
32
- return actual_value
33
- end
34
- end
35
-
36
- class Placeholder < UnaryElement
37
- def output(context, params)
38
- actual_value = retrieve_value_from(context, params)
39
-
40
- result = case actual_value
41
- when NilClass
42
- ''
43
- when Array
44
- actual_value.join('<br/>')
45
- when String
46
- actual_value
47
- else
48
- actual_value.to_s()
49
- end
50
-
51
- return result
52
- end
53
- end
54
-
55
- class EOLine
56
- def output(context, params)
57
- return "\n"
58
- end
59
- end
60
-
61
- class Section < UnaryElement
62
- attr_reader :children
63
-
64
- def initialize(name)
65
- super(name)
66
- @children = []
67
- end
68
-
69
- def add_child(child)
70
- children << child
71
- end
72
-
73
- def variables
74
- section_variables = children.each_with_object([]) do |child, result|
75
- case child
76
- when Placeholder
77
- result << child.name
78
- when Section
79
- result.concat(child.variables)
80
- else
81
- # noop
82
- end
83
- end
84
- return section_variables.flatten.uniq
85
- end
86
-
87
- def output(context, params)
88
- msg = "Method Section.#{__method__} must be implemented in subclass."
89
- raise NotImplementedError, msg
90
- end
91
- end
92
-
93
- class ConditionalSection < Section
94
- attr_reader :existence
95
-
96
- def initialize(name, generate)
97
- super(name)
98
- @existence = generate
99
- end
100
-
101
- def output(context, params)
102
- value = retrieve_value_from(context, params)
103
- if (!value.nil? && existence) || (value.nil? && !existence)
104
- result = children.each_with_object('') do |child, item|
105
- item << child.output(context, params)
106
- end
107
- else
108
- result = ''
109
- end
110
-
111
- return result
112
- end
113
-
114
- def to_s
115
- return "<?#{name}>"
116
- end
117
- end
118
-
119
- SectionEndMarker = Struct.new(:name)
120
-
121
- class Engine
122
- attr_reader :source
123
-
124
- # Invalid internal elements refers to spaces, any punctuation sign or
125
- # delimiter that is forbidden between chevrons <...> template tags.
126
- InvalidInternalElements = begin
127
- forbidden = ' !"#' + "$%&'()*+,-./:;<=>?[\\]^`{|}~"
128
- all_escaped = []
129
- forbidden.each_char() { |ch| all_escaped << Regexp.escape(ch) }
130
- pattern = all_escaped.join('|')
131
- Regexp.new(pattern)
132
- end
133
-
134
- def initialize(source)
135
- @source = source
136
-
137
- # The generated source contains an internal representation of the
138
- # given template text.
139
- @generated_source = generate(source)
140
- end
141
-
142
- # The parse mechanism is designed to break up TDL lines into static
143
- # and dynamic components. Dynamic components will correspond to
144
- # tagged elements of the string, which indicate parameters in the
145
- # original TDL phrase.
146
- def self.parse(line)
147
- scanner = StringScanner.new(line)
148
- result = []
149
-
150
- until scanner.eos?
151
- tag_literal = scanner.scan(/<(?:[^\\<>]|\\.)*>/)
152
-
153
- unless tag_literal.nil?
154
- result << [:dynamic, tag_literal.gsub(/^<|>$/, '')]
155
- end
156
-
157
- text_literal = scanner.scan(/(?:[^\\<>]|\\.)+/)
158
- result << [:static, text_literal] unless text_literal.nil?
159
-
160
- indicate_parsing_error(line) if tag_literal.nil? && text_literal.nil?
161
- end
162
-
163
- return result
164
- end
165
-
166
- def self.indicate_parsing_error(line)
167
- # The regular expression will be looking to match \< or \>. Those are
168
- # escaped chevrons and will be replaced.
169
- no_escaped = line.gsub(/\\[<>]/, '--')
170
- unbalance_count = 0
171
-
172
- no_escaped.each_char do |ch|
173
- case ch
174
- when '<'
175
- unbalance_count += 1
176
- when '>'
177
- unbalance_count -= 1
178
- end
179
-
180
- raise StandardError, "Nested opening chevron '<'." if unbalance_count > 1
181
- raise StandardError, "Missing opening chevron '<'." if unbalance_count < 0
182
- end
183
-
184
- raise StandardError, "Missing closing chevron '>'." if unbalance_count == 1
185
- end
186
-
187
- def parse_element(text)
188
- # Check if the text matched is a ? or a / character. If the next bit
189
- # of text after the ? or / is a invalid element of if there is an
190
- # invalid element at all, an error will be raised.
191
- if text =~ /^[\?\/]/
192
- matching = InvalidInternalElements.match(text[1..-1])
193
- else
194
- matching = InvalidInternalElements.match(text)
195
- end
196
-
197
- raise InvalidElementError.new(text, matching[0]) if matching
198
-
199
- result = case text[0, 1]
200
- when '/'
201
- SectionEndMarker.new(text[1..-1])
202
- when '?'
203
- ConditionalSection.new(text[1..-1], true)
204
- else
205
- Placeholder.new(text)
206
- end
207
-
208
- return result
209
- end
210
-
211
- # This method is used to retrieve all of the variable elements, which
212
- # will be placeholder names, that appear in the template.
213
- def variables
214
- @variables ||= begin
215
- vars = @generated_source.each_with_object([]) do |element, result|
216
- case element
217
- when Placeholder
218
- result << element.name
219
- when Section
220
- result.concat(element.variables)
221
- else
222
- # noop
223
- end
224
- end
225
- vars.flatten.uniq
226
- end
227
- return @variables
228
- end
229
-
230
- # This general output method will provide a final template within
231
- # the given scope object (Placeholder, StaticText, etc) and with
232
- # any of the parameters specified.
233
- def output(context, params)
234
- return '' if @generated_source.empty?
235
- result = @generated_source.each_with_object('') do |element, item|
236
- item << element.output(context, params)
237
- end
238
- return result
239
- end
240
-
241
- # To "generate" means to create an internal representation of
242
- # the template.
243
- def generate(source)
244
- input_lines = source.split(/\r\n?|\n/)
245
-
246
- raw_lines = input_lines.map do |line|
247
- line_items = self.class.parse(line)
248
- line_items.each do |(kind, text)|
249
- if (kind == :dynamic) && text.strip.empty?
250
- raise EmptyParameterError.new(line.strip)
251
- end
252
- end
253
- line_items
254
- end
255
-
256
- template_lines = raw_lines.map { |line| generate_line(line) }
257
- return generate_sections(template_lines.flatten)
258
- end
259
-
260
- def generate_line(line)
261
- line_rep = line.map { |item| generate_couple(item) }
262
- section_item = nil
263
-
264
- line_to_despace = line_rep.all? do |item|
265
- case item
266
- when StaticText
267
- item.source =~ /\s+/
268
- when Section, SectionEndMarker
269
- if section_item.nil?
270
- section_item = item
271
- true
272
- else
273
- false
274
- end
275
- else
276
- false
277
- end
278
- end
279
-
280
- if line_to_despace && ! section_item.nil?
281
- line_rep = [section_item]
282
- else
283
- line_rep_ending(line_rep)
284
- end
285
-
286
- return line_rep
287
- end
288
-
289
- def generate_couple(item)
290
- (kind, text) = item
291
-
292
- result = case kind
293
- when :static
294
- StaticText.new(text)
295
- when :dynamic
296
- parse_element(text)
297
- end
298
-
299
- return result
300
- end
301
-
302
- def generate_sections(sequence)
303
- open_sections = []
304
-
305
- generated = sequence.each_with_object([]) do |element, result|
306
- case element
307
- when Section
308
- open_sections << element
309
- when SectionEndMarker
310
- validate_section_end(element, open_sections)
311
- result << open_sections.pop()
312
- else
313
- if open_sections.empty?
314
- result << element
315
- else
316
- open_sections.last.add_child(element)
317
- end
318
- end
319
- end
320
-
321
- unless open_sections.empty?
322
- error_message = "Unterminated section #{open_sections.last}."
323
- raise StandardError, error_message
324
- end
325
-
326
- return generated
327
- end
328
-
329
- def validate_section_end(marker, sections)
330
- if sections.empty?
331
- msg = "End of section </#{marker.name}> found while no corresponding section is open."
332
- raise StandardError, msg
333
- end
334
-
335
- if marker.name != sections.last.name
336
- msg = "End of section </#{marker.name}> does not match current section '#{sections.last.name}'."
337
- raise StandardError, msg
338
- end
339
- end
340
-
341
- def line_rep_ending(line)
342
- if line.last.is_a?(SectionEndMarker)
343
- section_end = line.pop()
344
- line << EOLine.new
345
- line << section_end
346
- else
347
- line << EOLine.new
348
- end
349
- end
350
-
351
- end # class: Engine
352
-
353
- end
354
- end