lucid 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. data/.gitignore +30 -10
  2. data/.rspec +1 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +15 -0
  6. data/Gemfile +4 -2
  7. data/HISTORY.md +22 -0
  8. data/{LICENSE.txt → LICENSE} +6 -3
  9. data/README.md +22 -8
  10. data/Rakefile +2 -1
  11. data/bin/lucid +10 -10
  12. data/bin/lucid-gen +4 -0
  13. data/lib/autotest/discover.rb +11 -0
  14. data/lib/autotest/lucid.rb +6 -0
  15. data/lib/autotest/lucid_mixin.rb +135 -0
  16. data/lib/autotest/lucid_rails.rb +6 -0
  17. data/lib/autotest/lucid_rails_rspec.rb +6 -0
  18. data/lib/autotest/lucid_rails_rspec2.rb +6 -0
  19. data/lib/autotest/lucid_rspec.rb +6 -0
  20. data/lib/autotest/lucid_rspec2.rb +6 -0
  21. data/lib/lucid.rb +32 -1
  22. data/lib/lucid/ast.rb +20 -0
  23. data/lib/lucid/ast/background.rb +116 -0
  24. data/lib/lucid/ast/comment.rb +24 -0
  25. data/lib/lucid/ast/doc_string.rb +44 -0
  26. data/lib/lucid/ast/empty_background.rb +33 -0
  27. data/lib/lucid/ast/examples.rb +49 -0
  28. data/lib/lucid/ast/feature.rb +99 -0
  29. data/lib/lucid/ast/has_steps.rb +74 -0
  30. data/lib/lucid/ast/location.rb +41 -0
  31. data/lib/lucid/ast/multiline_argument.rb +31 -0
  32. data/lib/lucid/ast/names.rb +13 -0
  33. data/lib/lucid/ast/outline_table.rb +194 -0
  34. data/lib/lucid/ast/scenario.rb +103 -0
  35. data/lib/lucid/ast/scenario_outline.rb +144 -0
  36. data/lib/lucid/ast/specs.rb +38 -0
  37. data/lib/lucid/ast/step.rb +122 -0
  38. data/lib/lucid/ast/step_collection.rb +92 -0
  39. data/lib/lucid/ast/step_invocation.rb +196 -0
  40. data/lib/lucid/ast/table.rb +730 -0
  41. data/lib/lucid/ast/tags.rb +28 -0
  42. data/lib/lucid/ast/tdl_walker.rb +195 -0
  43. data/lib/lucid/cli/app.rb +78 -0
  44. data/lib/lucid/cli/configuration.rb +261 -0
  45. data/lib/lucid/cli/options.rb +463 -0
  46. data/lib/lucid/cli/profile.rb +101 -0
  47. data/lib/lucid/configuration.rb +53 -0
  48. data/lib/lucid/core_ext/disable_autorunners.rb +15 -0
  49. data/lib/lucid/core_ext/instance_exec.rb +70 -0
  50. data/lib/lucid/core_ext/proc.rb +36 -0
  51. data/lib/lucid/core_ext/string.rb +9 -0
  52. data/lib/lucid/errors.rb +40 -0
  53. data/lib/lucid/factory.rb +43 -0
  54. data/lib/lucid/formatter/ansicolor.rb +168 -0
  55. data/lib/lucid/formatter/console.rb +218 -0
  56. data/lib/lucid/formatter/debug.rb +33 -0
  57. data/lib/lucid/formatter/duration.rb +11 -0
  58. data/lib/lucid/formatter/gherkin_formatter_adapter.rb +94 -0
  59. data/lib/lucid/formatter/gpretty.rb +24 -0
  60. data/lib/lucid/formatter/html.rb +610 -0
  61. data/lib/lucid/formatter/interceptor.rb +66 -0
  62. data/lib/lucid/formatter/io.rb +31 -0
  63. data/lib/lucid/formatter/jquery-min.js +154 -0
  64. data/lib/lucid/formatter/json.rb +19 -0
  65. data/lib/lucid/formatter/json_pretty.rb +10 -0
  66. data/lib/lucid/formatter/junit.rb +177 -0
  67. data/lib/lucid/formatter/lucid.css +283 -0
  68. data/lib/lucid/formatter/lucid.sass +244 -0
  69. data/lib/lucid/formatter/ordered_xml_markup.rb +24 -0
  70. data/lib/lucid/formatter/progress.rb +95 -0
  71. data/lib/lucid/formatter/rerun.rb +91 -0
  72. data/lib/lucid/formatter/standard.rb +235 -0
  73. data/lib/lucid/formatter/stepdefs.rb +14 -0
  74. data/lib/lucid/formatter/steps.rb +49 -0
  75. data/lib/lucid/formatter/summary.rb +35 -0
  76. data/lib/lucid/formatter/unicode.rb +53 -0
  77. data/lib/lucid/formatter/usage.rb +132 -0
  78. data/lib/lucid/generator.rb +21 -0
  79. data/lib/lucid/generators/project.rb +70 -0
  80. data/lib/lucid/generators/project/Gemfile.tt +6 -0
  81. data/lib/lucid/generators/project/browser-symbiont.rb +24 -0
  82. data/lib/lucid/generators/project/driver-symbiont.rb +4 -0
  83. data/lib/lucid/generators/project/errors.rb +26 -0
  84. data/lib/lucid/generators/project/events-symbiont.rb +36 -0
  85. data/lib/lucid/generators/project/lucid-symbiont.yml +6 -0
  86. data/lib/lucid/interface.rb +8 -0
  87. data/lib/lucid/interface_methods.rb +125 -0
  88. data/lib/lucid/interface_rb/matcher.rb +108 -0
  89. data/lib/lucid/interface_rb/rb_hook.rb +18 -0
  90. data/lib/lucid/interface_rb/rb_language.rb +190 -0
  91. data/lib/lucid/interface_rb/rb_lucid.rb +119 -0
  92. data/lib/lucid/interface_rb/rb_step_definition.rb +122 -0
  93. data/lib/lucid/interface_rb/rb_transform.rb +57 -0
  94. data/lib/lucid/interface_rb/rb_world.rb +136 -0
  95. data/lib/lucid/interface_rb/regexp_argument_matcher.rb +21 -0
  96. data/lib/lucid/load_path.rb +13 -0
  97. data/lib/lucid/parser.rb +2 -126
  98. data/lib/lucid/platform.rb +27 -0
  99. data/lib/lucid/rspec/allow_doubles.rb +20 -0
  100. data/lib/lucid/rspec/disallow_options.rb +27 -0
  101. data/lib/lucid/runtime.rb +200 -0
  102. data/lib/lucid/runtime/facade.rb +60 -0
  103. data/lib/lucid/runtime/interface_io.rb +60 -0
  104. data/lib/lucid/runtime/orchestrator.rb +218 -0
  105. data/lib/lucid/runtime/results.rb +64 -0
  106. data/lib/lucid/runtime/specs_loader.rb +79 -0
  107. data/lib/lucid/spec_file.rb +112 -0
  108. data/lib/lucid/step_definition_light.rb +20 -0
  109. data/lib/lucid/step_definitions.rb +13 -0
  110. data/lib/lucid/step_match.rb +99 -0
  111. data/lib/lucid/tdl_builder.rb +282 -0
  112. data/lib/lucid/term/ansicolor.rb +118 -0
  113. data/lib/lucid/unit.rb +11 -0
  114. data/lib/lucid/wire_support/configuration.rb +38 -0
  115. data/lib/lucid/wire_support/connection.rb +61 -0
  116. data/lib/lucid/wire_support/request_handler.rb +32 -0
  117. data/lib/lucid/wire_support/wire_exception.rb +32 -0
  118. data/lib/lucid/wire_support/wire_language.rb +54 -0
  119. data/lib/lucid/wire_support/wire_packet.rb +34 -0
  120. data/lib/lucid/wire_support/wire_protocol.rb +43 -0
  121. data/lib/lucid/wire_support/wire_protocol/requests.rb +125 -0
  122. data/lib/lucid/wire_support/wire_step_definition.rb +26 -0
  123. data/lucid.gemspec +25 -14
  124. metadata +220 -12
  125. data/lib/lucid/app.rb +0 -103
  126. data/lib/lucid/options.rb +0 -168
  127. data/lib/lucid/version.rb +0 -3
  128. data/lucid.yml +0 -8
@@ -0,0 +1,60 @@
1
+ require 'timeout'
2
+
3
+ module Lucid
4
+ class Runtime
5
+
6
+ module InterfaceIO
7
+ attr_writer :visitor
8
+
9
+ def puts(*messages)
10
+ @visitor.puts(*messages)
11
+ end
12
+
13
+ def ask(question, timeout_seconds)
14
+ STDOUT.puts(question)
15
+ STDOUT.flush
16
+ puts(question)
17
+
18
+ if(Lucid::JRUBY)
19
+ answer = jruby_gets(timeout_seconds)
20
+ else
21
+ answer = mri_gets(timeout_seconds)
22
+ end
23
+
24
+ if(answer)
25
+ puts(answer)
26
+ answer
27
+ else
28
+ raise("Lucid waited for input for #{timeout_seconds} seconds, then timed out.")
29
+ end
30
+ end
31
+
32
+ def embed(src, mime_type, label)
33
+ @visitor.embed(src, mime_type, label)
34
+ end
35
+
36
+ private
37
+
38
+ def mri_gets(timeout_seconds)
39
+ begin
40
+ Timeout.timeout(timeout_seconds) do
41
+ STDIN.gets
42
+ end
43
+ rescue Timeout::Error
44
+ nil
45
+ end
46
+ end
47
+
48
+ def jruby_gets(timeout_seconds)
49
+ answer = nil
50
+ t = java.lang.Thread.new do
51
+ answer = STDIN.gets
52
+ end
53
+ t.start
54
+ t.join(timeout_seconds * 1000)
55
+ answer
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,218 @@
1
+ require 'lucid/factory'
2
+ require 'lucid/ast/multiline_argument'
3
+ require 'lucid/runtime/facade'
4
+
5
+ module Lucid
6
+
7
+ class Runtime
8
+
9
+ class Orchestrator
10
+
11
+ require 'forwardable'
12
+ class StepInvoker
13
+ include Gherkin::Rubify
14
+
15
+ def initialize(orchestrator)
16
+ @orchestrator = orchestrator
17
+ end
18
+
19
+ def uri(uri)
20
+ end
21
+
22
+ def step(step)
23
+ @orchestrator.invoke(step.name, AST::MultilineArgument.from(step.doc_string || step.rows))
24
+ end
25
+
26
+ def eof
27
+ end
28
+ end
29
+
30
+ include ObjectFactory
31
+
32
+ def initialize(user_interface, configuration={})
33
+ @configuration = Configuration.parse(configuration)
34
+ @runtime_facade = Runtime::Facade.new(self, user_interface)
35
+ @unsupported_languages = []
36
+ @supported_languages = []
37
+ @language_map = {}
38
+ end
39
+
40
+ def configure(new_configuration)
41
+ @configuration = Configuration.parse(new_configuration)
42
+ end
43
+
44
+ # Invokes a series of steps +steps_text+. Example:
45
+ #
46
+ # invoke(%Q{
47
+ # Given I have 8 cukes in my belly
48
+ # Then I should not be thirsty
49
+ # })
50
+ def invoke_steps(steps_text, i18n, file_colon_line)
51
+ file, line = file_colon_line.split(':')
52
+ parser = Gherkin::Parser::Parser.new(StepInvoker.new(self), true, 'steps', false, i18n.iso_code)
53
+ parser.parse(steps_text, file, line.to_i)
54
+ end
55
+
56
+ def invoke(step_name, multiline_argument=nil)
57
+ multiline_argument = Lucid::AST::MultilineArgument.from(multiline_argument)
58
+ # It is very important to leave multiline_argument=nil as a vararg. Cuke4Duke needs it that way.
59
+ begin
60
+ step_match(step_name).invoke(multiline_argument)
61
+ rescue Exception => e
62
+ e.nested! if Undefined === e
63
+ raise e
64
+ end
65
+ end
66
+
67
+ # The orchestrator will register the the code language and load up an
68
+ # implementation of that language. There is a provision to make sure
69
+ # that the language is not already registered.
70
+ def load_code_language(code)
71
+ return @language_map[code] if @language_map[code]
72
+ lucid_language = create_object_of("Lucid::Interface#{code.capitalize}::#{code.capitalize}Language")
73
+ language = lucid_language.new(@runtime_facade)
74
+ @supported_languages << language
75
+ @language_map[code] = language
76
+ language
77
+ end
78
+
79
+ # The orchestrator will load only the loadable execution context files.
80
+ # This is how the orchestrator will, quite literally, orchestrate the
81
+ # execution of specs with the code logic that supports those specs.
82
+ # @see Lucid::Runtime.load_execution_context
83
+ def load_files(files)
84
+ log.info("Orchestrator Load Files:\n")
85
+ files.each do |file|
86
+ load_file(file)
87
+ end
88
+ log.info("\n")
89
+ end
90
+
91
+ def load_files_from_paths(paths)
92
+ files = paths.map { |path| Dir["#{path}/**/*"] }.flatten
93
+ load_files files
94
+ end
95
+
96
+ def unmatched_step_definitions
97
+ @supported_languages.map do |programming_language|
98
+ programming_language.unmatched_step_definitions
99
+ end.flatten
100
+ end
101
+
102
+ def matcher_text(step_keyword, step_name, multiline_arg_class) #:nodoc:
103
+ load_code_language('rb') if unknown_programming_language?
104
+ @supported_languages.map do |programming_language|
105
+ programming_language.matcher_text(step_keyword, step_name, multiline_arg_class, @configuration.matcher_type)
106
+ end.join("\n")
107
+ end
108
+
109
+ def unknown_programming_language?
110
+ @supported_languages.empty?
111
+ end
112
+
113
+ def fire_hook(name, *args)
114
+ @supported_languages.each do |programming_language|
115
+ programming_language.send(name, *args)
116
+ end
117
+ end
118
+
119
+ def around(scenario, block)
120
+ @supported_languages.reverse.inject(block) do |blk, programming_language|
121
+ proc do
122
+ programming_language.around(scenario) do
123
+ blk.call(scenario)
124
+ end
125
+ end
126
+ end.call
127
+ end
128
+
129
+ def step_definitions
130
+ @supported_languages.map do |programming_language|
131
+ programming_language.step_definitions
132
+ end.flatten
133
+ end
134
+
135
+ def step_match(step_name, name_to_report=nil) #:nodoc:
136
+ @match_cache ||= {}
137
+
138
+ match = @match_cache[[step_name, name_to_report]]
139
+ return match if match
140
+
141
+ @match_cache[[step_name, name_to_report]] = step_match_without_cache(step_name, name_to_report)
142
+ end
143
+
144
+ private
145
+
146
+ def step_match_without_cache(step_name, name_to_report=nil)
147
+ matches = matches(step_name, name_to_report)
148
+ raise Undefined.new(step_name) if matches.empty?
149
+ matches = best_matches(step_name, matches) if matches.size > 1 && guess_step_matches?
150
+ raise Ambiguous.new(step_name, matches, guess_step_matches?) if matches.size > 1
151
+ matches[0]
152
+ end
153
+
154
+ def guess_step_matches?
155
+ @configuration.guess?
156
+ end
157
+
158
+ def matches(step_name, name_to_report)
159
+ @supported_languages.map do |programming_language|
160
+ programming_language.step_matches(step_name, name_to_report).to_a
161
+ end.flatten
162
+ end
163
+
164
+ def best_matches(step_name, step_matches) #:nodoc:
165
+ no_groups = step_matches.select {|step_match| step_match.args.length == 0}
166
+ max_arg_length = step_matches.map {|step_match| step_match.args.length }.max
167
+ top_groups = step_matches.select {|step_match| step_match.args.length == max_arg_length }
168
+
169
+ if no_groups.any?
170
+ longest_regexp_length = no_groups.map {|step_match| step_match.text_length }.max
171
+ no_groups.select {|step_match| step_match.text_length == longest_regexp_length }
172
+ elsif top_groups.any?
173
+ shortest_capture_length = top_groups.map {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } }.min
174
+ top_groups.select {|step_match| step_match.args.inject(0) {|sum, c| sum + c.to_s.length } == shortest_capture_length }
175
+ else
176
+ top_groups
177
+ end
178
+ end
179
+
180
+ # For each execution context file, the orchestrator will determine the
181
+ # code language associated with the file.
182
+ def load_file(file)
183
+ if language = get_language_for(file)
184
+ log.info(" * #{file}\n")
185
+ language.load_code_file(file)
186
+ else
187
+ log.info(" * #{file} [NOT SUPPORTED]\n")
188
+ end
189
+ end
190
+
191
+ def log
192
+ Lucid.logger
193
+ end
194
+
195
+ # The orchestrator will attempt to get the programming language for a
196
+ # specific code file, unless that code file is marked as being an
197
+ # unsupported language. An object is returned if the code file was part
198
+ # of a supported language. If an object is returned it will be an
199
+ # object of this sort:
200
+ # Lucid::InterfaceRb::RbLanguage
201
+ def get_language_for(file)
202
+ if extension = File.extname(file)[1..-1]
203
+ return nil if @unsupported_languages.index(extension)
204
+ begin
205
+ load_code_language(extension)
206
+ rescue LoadError => e
207
+ log.info("Unable to load '#{extension}' language for file #{file}: #{e.message}\n")
208
+ @unsupported_languages << extension
209
+ nil
210
+ end
211
+ else
212
+ nil
213
+ end
214
+ end
215
+
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,64 @@
1
+ module Lucid
2
+ class Runtime
3
+
4
+ class Results
5
+ def initialize(configuration)
6
+ @configuration = configuration
7
+
8
+ # Optimization - quicker lookup.
9
+ @inserted_steps = {}
10
+ @inserted_scenarios = {}
11
+ end
12
+
13
+ def configure(new_configuration)
14
+ @configuration = Configuration.parse(new_configuration)
15
+ end
16
+
17
+ def step_visited(step) #:nodoc:
18
+ step_id = step.object_id
19
+
20
+ unless @inserted_steps.has_key?(step_id)
21
+ @inserted_steps[step_id] = step
22
+ steps.push(step)
23
+ end
24
+ end
25
+
26
+ def scenario_visited(scenario) #:nodoc:
27
+ scenario_id = scenario.object_id
28
+
29
+ unless @inserted_scenarios.has_key?(scenario_id)
30
+ @inserted_scenarios[scenario_id] = scenario
31
+ scenarios.push(scenario)
32
+ end
33
+ end
34
+
35
+ def steps(status = nil) #:nodoc:
36
+ @steps ||= []
37
+ if(status)
38
+ @steps.select{|step| step.status == status}
39
+ else
40
+ @steps
41
+ end
42
+ end
43
+
44
+ def scenarios(status = nil) #:nodoc:
45
+ @scenarios ||= []
46
+ if(status)
47
+ @scenarios.select{|scenario| scenario.status == status}
48
+ else
49
+ @scenarios
50
+ end
51
+ end
52
+
53
+ def failure?
54
+ if @configuration.wip?
55
+ scenarios(:passed).any?
56
+ else
57
+ scenarios(:failed).any? || steps(:failed).any? ||
58
+ (@configuration.strict? && (steps(:undefined).any? || steps(:pending).any?))
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,79 @@
1
+ require 'lucid/errors'
2
+
3
+ module Lucid
4
+ class Runtime
5
+ class SpecsLoader
6
+ include Formatter::Duration
7
+
8
+ def initialize(spec_files, filters, tag_expression)
9
+ @spec_files, @filters, @tag_expression = spec_files, filters, tag_expression
10
+ end
11
+
12
+ # @see Lucid::Runtime.specs
13
+ def specs
14
+ load unless (defined? @specs) and @specs
15
+ @specs
16
+ end
17
+
18
+ private
19
+
20
+ # The specs loader will call upon load to load up all specs that were
21
+ # found in the spec repository. During this process, a Specs instance
22
+ # is created that will hold instances of the high level construct,
23
+ # which is basically the feature.
24
+ def load
25
+ specs = AST::Specs.new
26
+
27
+ # Note that "specs" is going to be an instance of Lucid::AST::Specs.
28
+ # It will contain a @specs instance variable that is going to contain
29
+ # an specs found.
30
+
31
+ tag_counts = {}
32
+ start = Time.new
33
+ log.info("Specs:\n")
34
+
35
+ @spec_files.each do |f|
36
+ spec_file = SpecFile.new(f)
37
+
38
+ # The "spec_file" will contain a Lucid::SpecFile instance, a
39
+ # primary attribute of which will be a @location instance variable.
40
+
41
+ spec = spec_file.parse(@filters, tag_counts)
42
+
43
+ # The "spec" will contain an instance of Lucid::AST::Feature.
44
+
45
+ if spec
46
+ specs.add_feature(spec)
47
+ log.info(" * #{f}\n")
48
+ end
49
+ end
50
+
51
+ duration = Time.now - start
52
+ log.info("Parsing spec files took #{format_duration(duration)}\n\n")
53
+
54
+ check_tag_limits(tag_counts)
55
+
56
+ @specs = specs
57
+ end
58
+
59
+ def check_tag_limits(tag_counts)
60
+ error_messages = []
61
+ @tag_expression.limits.each do |tag_name, tag_limit|
62
+ tag_locations = (tag_counts[tag_name] || [])
63
+ tag_count = tag_locations.length
64
+ if tag_count > tag_limit
65
+ error = "#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
66
+ tag_locations.join("\n ")
67
+ error_messages << error
68
+ end
69
+ end
70
+ raise TagExcess.new(error_messages) if error_messages.any?
71
+ end
72
+
73
+ def log
74
+ Lucid.logger
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,112 @@
1
+ require 'lucid/tdl_builder'
2
+ require 'gherkin/formatter/filter_formatter'
3
+ require 'gherkin/formatter/tag_count_formatter'
4
+ require 'gherkin/parser/parser'
5
+
6
+ module Lucid
7
+ class SpecFile
8
+ SPEC_PATTERN = /^([\w\W]*?):([\d:]+)$/ #:nodoc:
9
+ DEFAULT_ENCODING = "UTF-8" #:nodoc:
10
+ NON_EXEC_PATTERN = /^\s*#|^\s*$/ #:nodoc:
11
+ ENCODING_PATTERN = /^\s*#\s*encoding\s*:\s*([^\s]+)/ #:nodoc:
12
+
13
+ # The +uri+ argument is the location of the source. It can be a path
14
+ # or a path:line1:line2 etc.
15
+ def initialize(uri, source=nil)
16
+ @source = source
17
+ _, @path, @lines = *SPEC_PATTERN.match(uri)
18
+ if @path
19
+ @lines = @lines.split(':').map { |line| line.to_i }
20
+ else
21
+ @path = uri
22
+ end
23
+ end
24
+
25
+ # The parse action will parse a specific spec source and will return
26
+ # a the high level construct of the spec. If any filters are passed
27
+ # in, the result will be filtered accordingly.
28
+ # @see Lucid::Runtime::SpecsLoader.load
29
+ def parse(specified_filters, tag_counts)
30
+ filters = @lines || specified_filters
31
+
32
+ tdl_builder = Lucid::Parser::TDLBuilder.new(@path)
33
+ filter_formatter = filters.empty? ? tdl_builder : Gherkin::Formatter::FilterFormatter.new(tdl_builder, filters)
34
+ tag_count_formatter = Gherkin::Formatter::TagCountFormatter.new(filter_formatter, tag_counts)
35
+
36
+ # Gherkin Parser parameters:
37
+ # formatter, raise_on_error, machine_name, force_ruby
38
+ # The machine name refers to a state machine table.
39
+ parser = Gherkin::Parser::Parser.new(tag_count_formatter, true, "root", false)
40
+
41
+ begin
42
+ # parse parameters:
43
+ # gherkin, feature_uri, line_offset
44
+ parser.parse(source, @path, 0)
45
+ tdl_builder.language = parser.i18n_language
46
+ tdl_builder.result
47
+ rescue Gherkin::Lexer::LexingError, Gherkin::Parser::ParseError => e
48
+ e.message.insert(0, "#{@path}: ")
49
+ raise e
50
+ end
51
+ end
52
+
53
+ # The source method is used to return a properly encoded spec file.
54
+ # If the spec source read in declares a different encoding, then this
55
+ # method will make sure to use Lucid's default encoding.
56
+ def source
57
+ @source ||= if @path =~ /^http/
58
+ require 'open-uri'
59
+ open(@path).read
60
+ else
61
+ begin
62
+ source = File.open(@path, Lucid.file_mode('r', DEFAULT_ENCODING)).read
63
+ encoding = encoding_for(source)
64
+ if(DEFAULT_ENCODING.downcase != encoding.downcase)
65
+ source = File.open(@path, Lucid.file_mode('r', encoding)).read
66
+ source = to_default_encoding(source, encoding)
67
+ end
68
+ source
69
+ rescue Errno::EACCES => e
70
+ e.message << "\nLucid was unable to access #{File.expand_path(@path)}."
71
+ raise e
72
+ rescue Errno::ENOENT => e
73
+ if @path == "specs"
74
+ e.message << ["\nYou don't have a 'specs' directory. This is the default specification",
75
+ "directory that Lucid will use if one is not specified. So either create",
76
+ "that directory or specify where your test repository is located."].join("\n")
77
+ else
78
+ e.message << ["\nThere is no '#{@path}' directory. Since that is what you specified as",
79
+ "your spec repository, this directory must be present."].join("\n")
80
+ end
81
+ raise e
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def encoding_for(source)
89
+ encoding = DEFAULT_ENCODING
90
+
91
+ source.each_line do |line|
92
+ break unless NON_EXEC_PATTERN =~ line
93
+ if ENCODING_PATTERN =~ line
94
+ encoding = $1
95
+ break
96
+ end
97
+ end
98
+
99
+ encoding
100
+ end
101
+
102
+ def to_default_encoding(string, encoding)
103
+ if string.respond_to?(:encode)
104
+ string.encode(DEFAULT_ENCODING)
105
+ else
106
+ require 'iconv'
107
+ Iconv.new(DEFAULT_ENCODING, encoding).iconv(string)
108
+ end
109
+ end
110
+
111
+ end
112
+ end